RANGER-4328: updated resource matcher handling of SELF_OR_PREFIX scope
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcher.java b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcher.java
index cb8d46a..5eee8d1 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcher.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcher.java
@@ -30,15 +30,18 @@
 import org.apache.commons.lang.StringUtils;
 import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyResource;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType;
 import org.apache.ranger.plugin.util.ServiceDefUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType.*;
 
 public abstract class RangerAbstractResourceMatcher implements RangerResourceMatcher {
 	private static final Logger LOG = LoggerFactory.getLogger(RangerAbstractResourceMatcher.class);
 
-	public final static String WILDCARD_ASTERISK = "*";
+	public final static String WILDCARD_ASTERISK      = "*";
+	public final static String WILDCARD_QUESTION_MARK = "?";
 
 	public final static String OPTION_IGNORE_CASE             = "ignoreCase";
 	public final static String OPTION_QUOTED_CASE_SENSITIVE   = "quotedCaseSensitive";
@@ -324,6 +327,12 @@ public boolean applyExcludes(boolean allValuesRequested, boolean resultWithoutEx
 		return !resultWithoutExcludes;                                         // all other cases flip it
 	}
 
+	public ResourceElementMatchType applyExcludes(boolean allValuesRequested, ResourceElementMatchType resultWithoutExcludes) {
+		if (!policyIsExcludes) return resultWithoutExcludes;                   // not an excludes policy!
+		if (allValuesRequested && !isMatchAny)  return resultWithoutExcludes;  // one case where excludes has no effect
+		return resultWithoutExcludes == NONE ? SELF : NONE;                    // all other cases flip it
+	}
+
 	ResourceMatcher getMatcher(String policyValue) {
 		final int len = policyValue != null ? policyValue.length() : 0;
 
@@ -391,18 +400,8 @@ ResourceMatcher getMatcher(String policyValue) {
 }
 
 abstract class AbstractStringResourceMatcher extends ResourceMatcher {
-	private final boolean isCaseSensitive;
-
-	protected AbstractStringResourceMatcher(String value, Map<String, String> options, boolean isCaseSensitive) {
+	protected AbstractStringResourceMatcher(String value, Map<String, String> options) {
 		super(value, options);
-
-		this.isCaseSensitive = isCaseSensitive;
-	}
-
-	@Override
-	public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
-		return isCaseSensitive ? StringUtils.startsWith(getExpandedValue(evalContext), resourceValue)
-				               : StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue);
 	}
 
 	@Override
@@ -413,23 +412,35 @@ public boolean isChildMatch(String resourceValue, Map<String, Object> evalContex
 
 final class CaseSensitiveStringMatcher extends AbstractStringResourceMatcher {
 	CaseSensitiveStringMatcher(String value, Map<String, String> options) {
-		super(value, options, true);
+		super(value, options);
 	}
 
 	@Override
 	boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		return StringUtils.equals(resourceValue, getExpandedValue(evalContext));
 	}
+
+	@Override
+	public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+		return StringUtils.startsWith(getExpandedValue(evalContext), resourceValue);
+	}
+
 	int getPriority() { return 1 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);}
 }
 
 final class CaseInsensitiveStringMatcher extends AbstractStringResourceMatcher {
-	CaseInsensitiveStringMatcher(String value, Map<String, String> options) { super(value, options, false); }
+	CaseInsensitiveStringMatcher(String value, Map<String, String> options) { super(value, options); }
 
 	@Override
 	boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		return StringUtils.equalsIgnoreCase(resourceValue, getExpandedValue(evalContext));
 	}
+
+	@Override
+	public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+		return StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue);
+	}
+
 	int getPriority() {return 2 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
 }
 
@@ -437,7 +448,7 @@ final class QuotedCaseSensitiveStringMatcher extends AbstractStringResourceMatch
 	private final String quoteChars;
 
 	QuotedCaseSensitiveStringMatcher(String value, Map<String, String> options, String quoteChars) {
-		super(value, options, true);
+		super(value, options);
 
 		this.quoteChars = quoteChars;
 	}
@@ -451,28 +462,49 @@ boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		}
 	}
 
+	@Override
+	public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+		if (startsWithAnyChar(resourceValue, quoteChars)) {
+			return StringUtils.startsWith(getExpandedValue(evalContext), resourceValue);
+		} else {
+			return StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue);
+		}
+	}
+
 	int getPriority() {return 2 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
 }
 
 final class CaseSensitiveStartsWithMatcher extends AbstractStringResourceMatcher {
 	CaseSensitiveStartsWithMatcher(String value, Map<String, String> options) {
-		super(value, options, true);
+		super(value, options);
 	}
 
 	@Override
 	boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		return StringUtils.startsWith(resourceValue, getExpandedValue(evalContext));
 	}
+
+	@Override
+	public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+		return StringUtils.startsWith(getExpandedValue(evalContext), resourceValue);
+	}
+
 	int getPriority() { return 3 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);}
 }
 
 final class CaseInsensitiveStartsWithMatcher extends AbstractStringResourceMatcher {
-	CaseInsensitiveStartsWithMatcher(String value, Map<String, String> options) { super(value, options, false); }
+	CaseInsensitiveStartsWithMatcher(String value, Map<String, String> options) { super(value, options); }
 
 	@Override
 	boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		return StringUtils.startsWithIgnoreCase(resourceValue, getExpandedValue(evalContext));
 	}
+
+	@Override
+	public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+		return StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue);
+	}
+
 	int getPriority() { return 4 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
 }
 
@@ -480,7 +512,7 @@ final class QuotedCaseSensitiveStartsWithMatcher extends AbstractStringResourceM
 	private final String quoteChars;
 
 	QuotedCaseSensitiveStartsWithMatcher(String value, Map<String, String> options, String quoteChars) {
-		super(value, options, true);
+		super(value, options);
 
 		this.quoteChars = quoteChars;
 	}
@@ -494,30 +526,51 @@ boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		}
 	}
 
+	@Override
+	public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+		if (startsWithAnyChar(resourceValue, quoteChars)) {
+			return StringUtils.startsWith(getExpandedValue(evalContext), resourceValue);
+		} else {
+			return StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue);
+		}
+	}
+
 	int getPriority() { return 4 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
 }
 
 final class CaseSensitiveEndsWithMatcher extends AbstractStringResourceMatcher {
 	CaseSensitiveEndsWithMatcher(String value, Map<String, String> options) {
-		super(value, options, true);
+		super(value, options);
 	}
 
 	@Override
 	boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		return StringUtils.endsWith(resourceValue, getExpandedValue(evalContext));
 	}
+
+	@Override
+	public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+		return true; // isPrefixMatch() is always true for endsWith
+	}
+
 	int getPriority() { return 3 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
 }
 
 final class CaseInsensitiveEndsWithMatcher extends AbstractStringResourceMatcher {
 	CaseInsensitiveEndsWithMatcher(String value, Map<String, String> options) {
-		super(value, options, false);
+		super(value, options);
 	}
 
 	@Override
 	boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		return StringUtils.endsWithIgnoreCase(resourceValue, getExpandedValue(evalContext));
 	}
+
+	@Override
+	public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+		return true; // isPrefixMatch() is always true for endsWith
+	}
+
 	int getPriority() { return 4 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
 }
 
@@ -525,7 +578,7 @@ final class QuotedCaseSensitiveEndsWithMatcher extends AbstractStringResourceMat
 	private final String quoteChars;
 
 	QuotedCaseSensitiveEndsWithMatcher(String value, Map<String, String> options, String quoteChars) {
-		super(value, options, true);
+		super(value, options);
 
 		this.quoteChars = quoteChars;
 	}
@@ -539,31 +592,48 @@ boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		}
 	}
 
+	@Override
+	public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+		return true; // isPrefixMatch() is always true for endsWith
+	}
+
 	int getPriority() { return 4 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
 }
 
 final class CaseSensitiveWildcardMatcher extends AbstractStringResourceMatcher {
 	CaseSensitiveWildcardMatcher(String value, Map<String, String> options) {
-		super(value, options, true);
+		super(value, options);
 	}
 
 	@Override
 	boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		return FilenameUtils.wildcardMatch(resourceValue, getExpandedValue(evalContext), IOCase.SENSITIVE);
 	}
+
+	@Override
+	public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+		return ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), IOCase.SENSITIVE);
+	}
+
 	int getPriority() { return 5 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
 }
 
 
 final class CaseInsensitiveWildcardMatcher extends AbstractStringResourceMatcher {
 	CaseInsensitiveWildcardMatcher(String value, Map<String, String> options) {
-		super(value, options, false);
+		super(value, options);
 	}
 
 	@Override
 	boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		return FilenameUtils.wildcardMatch(resourceValue, getExpandedValue(evalContext), IOCase.INSENSITIVE);
 	}
+
+	@Override
+	public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+		return ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), IOCase.INSENSITIVE);
+	}
+
 	int getPriority() {return 6 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
 }
 
@@ -571,7 +641,7 @@ final class QuotedCaseSensitiveWildcardMatcher extends AbstractStringResourceMat
 	private final String quoteChars;
 
 	QuotedCaseSensitiveWildcardMatcher(String value, Map<String, String> options, String quoteChars) {
-		super(value, options, true);
+		super(value, options);
 
 		this.quoteChars = quoteChars;
 	}
@@ -583,6 +653,13 @@ boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		return FilenameUtils.wildcardMatch(resourceValue, getExpandedValue(evalContext), caseSensitivity);
 	}
 
+	@Override
+	public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+		IOCase caseSensitivity = startsWithAnyChar(resourceValue, quoteChars) ? IOCase.SENSITIVE : IOCase.INSENSITIVE;
+
+		return ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), caseSensitivity);
+	}
+
 	int getPriority() {return 6 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
 }
 
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcher.java b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcher.java
index 702cb27..977fd49 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcher.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcher.java
@@ -21,12 +21,15 @@
 
 
 import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchingScope;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.Collection;
 import java.util.Map;
 
+import static org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType.NONE;
+
 
 public class RangerDefaultResourceMatcher extends RangerAbstractResourceMatcher {
 	private static final Logger LOG = LoggerFactory.getLogger(RangerDefaultResourceMatcher.class);
@@ -37,43 +40,8 @@ public boolean isMatch(Object resource, ResourceElementMatchingScope matchingSco
 			LOG.debug("==> RangerDefaultResourceMatcher.isMatch(" + resource + ", " + evalContext + ")");
 		}
 
-		boolean ret = false;
-		boolean allValuesRequested = isAllValuesRequested(resource);
-		boolean isPrefixMatch = matchingScope == ResourceElementMatchingScope.SELF_OR_PREFIX;
-
-		if (isMatchAny || (allValuesRequested && !isPrefixMatch)) {
-			ret = isMatchAny;
-		} else {
-			if (resource instanceof String) {
-				String strValue = (String) resource;
-
-				for (ResourceMatcher resourceMatcher : resourceMatchers.getResourceMatchers()) {
-					ret = resourceMatcher.isMatch(strValue, matchingScope, evalContext);
-
-					if (ret) {
-						break;
-					}
-				}
-			} else if (resource instanceof Collection) {
-				@SuppressWarnings("unchecked")
-				Collection<String> resourceValues = (Collection<String>) resource;
-
-				for (ResourceMatcher resourceMatcher : resourceMatchers.getResourceMatchers()) {
-					for (String resourceValue : resourceValues) {
-						ret = resourceMatcher.isMatch(resourceValue, matchingScope, evalContext);
-
-						if (ret) {
-							break;
-						}
-					}
-					if (ret) {
-						break;
-					}
-				}
-			}
-		}
-
-		ret = applyExcludes(allValuesRequested, ret);
+		ResourceElementMatchType matchType = getMatchType(resource, matchingScope, evalContext);
+		boolean                  ret       = ResourceMatcher.isMatch(matchType, matchingScope);
 
 		if (ret == false) {
 			if(LOG.isDebugEnabled()) {
@@ -96,6 +64,66 @@ public boolean isMatch(Object resource, ResourceElementMatchingScope matchingSco
 		return ret;
 	}
 
+	@Override
+	public ResourceElementMatchType getMatchType(Object resource, ResourceElementMatchingScope matchingScope, Map<String, Object> evalContext) {
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("==> RangerDefaultResourceMatcher.getMatchType(" + resource + ", " + evalContext + ")");
+		}
+
+		ResourceElementMatchType ret                = NONE;
+		boolean                  allValuesRequested = isAllValuesRequested(resource);
+		boolean                  isPrefixMatch      = matchingScope == ResourceElementMatchingScope.SELF_OR_PREFIX;
+
+		if (isMatchAny || (allValuesRequested && !isPrefixMatch)) {
+			ret = isMatchAny ? ResourceElementMatchType.SELF : NONE;
+		} else {
+			if (resource instanceof String) {
+				String strValue = (String) resource;
+
+				for (ResourceMatcher resourceMatcher : resourceMatchers.getResourceMatchers()) {
+					ResourceElementMatchType matchType = resourceMatcher.getMatchType(strValue, matchingScope, evalContext);
+
+					if (matchType != NONE) {
+						ret = matchType;
+					}
+
+					if (ret == ResourceElementMatchType.SELF) {
+						break;
+					}
+				}
+			} else if (resource instanceof Collection) {
+				@SuppressWarnings("unchecked")
+				Collection<String> resourceValues = (Collection<String>) resource;
+
+				for (ResourceMatcher resourceMatcher : resourceMatchers.getResourceMatchers()) {
+					for (String resourceValue : resourceValues) {
+						ResourceElementMatchType matchType = resourceMatcher.getMatchType(resourceValue, matchingScope, evalContext);
+
+						if (matchType != NONE) {
+							ret = matchType;
+						}
+
+						if (ret == ResourceElementMatchType.SELF) {
+							break;
+						}
+					}
+
+					if (ret == ResourceElementMatchType.SELF) {
+						break;
+					}
+				}
+			}
+		}
+
+		ret = applyExcludes(allValuesRequested, ret);
+
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("<== RangerDefaultResourceMatcher.getMatchType(" + resource + ", " + evalContext + "): " + ret);
+		}
+
+		return ret;
+	}
+
 	public StringBuilder toString(StringBuilder sb) {
 		sb.append("RangerDefaultResourceMatcher={");
 
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcher.java b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcher.java
index 3c1523c..d376e90 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcher.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcher.java
@@ -130,7 +130,7 @@ ResourceMatcher getMatcher(String policyValue) {
 		if (isWildcardPresent) {
 			ret = new RecursiveWildcardResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase, RangerPathResourceMatcher::isRecursiveWildCardMatch, optIgnoreCase ? 8 : 7);
 		} else {
-			ret = new RecursivePathResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase ? StringUtils::equalsIgnoreCase : StringUtils::equals, optIgnoreCase ? StringUtils::startsWithIgnoreCase : StringUtils::startsWith, optIgnoreCase ? 8 : 7);
+			ret = new RecursivePathResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase, optIgnoreCase ? 8 : 7);
 		}
 
 		if (optReplaceTokens) {
@@ -263,15 +263,15 @@ private ResourceMatcher getPathMatcher(String policyValue) {
 		if (needWildcardMatch) { // test?, test*a*, test*a*b, *test*a
 			ret = new WildcardResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase, FilenameUtils::wildcardMatch, 6);
 		} else if (wildcardStartIdx == -1) { // test, testa, testab
-			ret = new PathResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase ? StringUtils::equalsIgnoreCase : StringUtils::equals, optIgnoreCase ? 2 : 1, !optIgnoreCase);
+			ret = new PathResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase ? StringUtils::equalsIgnoreCase : StringUtils::equals, !optIgnoreCase, optIgnoreCase ? 2 : 1);
 		} else if (wildcardStartIdx == 0) { // *test, **test, *testa, *testab
 			String matchStr = policyValue.substring(wildcardEndIdx + 1);
-			ret = new PathResourceMatcher(matchStr, getOptions(), pathSeparatorChar, optIgnoreCase ? StringUtils::endsWithIgnoreCase : StringUtils::endsWith, optIgnoreCase ? 4 : 3, !optIgnoreCase);
+			ret = new PathEndsWithResourceMatcher(matchStr, getOptions(), pathSeparatorChar, !optIgnoreCase, optIgnoreCase ? 4 : 3);
 		} else if (wildcardEndIdx != (len - 1)) { // test*a, test*ab
 			ret = new WildcardResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase, FilenameUtils::wildcardMatch, 6);
 		} else { // test*, test**, testa*, testab*
 			String matchStr = policyValue.substring(0, wildcardStartIdx);
-			ret = new PathResourceMatcher(matchStr, getOptions(), pathSeparatorChar, optIgnoreCase ? StringUtils::startsWithIgnoreCase : StringUtils::startsWith, optIgnoreCase ? 4 : 3, !optIgnoreCase);
+			ret = new PathStartsWithResourceMatcher(matchStr, getOptions(), pathSeparatorChar, !optIgnoreCase, optIgnoreCase ? 4 : 3);
 		}
 
 		if (optReplaceTokens) {
@@ -298,7 +298,7 @@ static abstract class AbstractPathResourceMatcher extends ResourceMatcher {
 		final int     priority;
 		final boolean isCaseSensitive;
 
-		AbstractPathResourceMatcher(String value, Map<String, String> options, char pathSeparatorChar, int priority, boolean isCaseSensitive) {
+		AbstractPathResourceMatcher(String value, Map<String, String> options, char pathSeparatorChar, boolean isCaseSensitive, int priority) {
 			super(value, options);
 
 			this.pathSeparatorChar = pathSeparatorChar;
@@ -308,19 +308,13 @@ static abstract class AbstractPathResourceMatcher extends ResourceMatcher {
 		int getPriority() {
 			return priority + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);
 		}
-
-		@Override
-		public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
-			return isCaseSensitive ? StringUtils.startsWith(getExpandedValue(evalContext), resourceValue)
-			                       : StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue);
-		}
 	}
 
 	static class PathResourceMatcher extends AbstractPathResourceMatcher {
 		final BiFunction<String, String, Boolean> function;
 
-		PathResourceMatcher(String value, Map<String, String> options, char pathSeparatorChar, BiFunction<String, String, Boolean> function, int priority, boolean isCaseSensitive) {
-			super(value, options, pathSeparatorChar, priority, isCaseSensitive);
+		PathResourceMatcher(String value, Map<String, String> options, char pathSeparatorChar, BiFunction<String, String, Boolean> function, boolean isCaseSensitive, int priority) {
+			super(value, options, pathSeparatorChar, isCaseSensitive, priority);
 
 			this.function = function;
 		}
@@ -328,21 +322,42 @@ static class PathResourceMatcher extends AbstractPathResourceMatcher {
 		@Override
 		boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 			if (LOG.isDebugEnabled()) {
-				LOG.debug("==> PathResourceMatcher.isMatch(resourceValue=" + resourceValue + ", evalContext=" + evalContext + ")");
+				LOG.debug("==> PathResourceMatcher.isMatch(resourceValue={}, evalContext={})", resourceValue, evalContext);
 			}
 
 			String  expandedValue = getExpandedValue(evalContext);
 			boolean ret           = function.apply(resourceValue, expandedValue);
 
 			if (LOG.isDebugEnabled()) {
-				LOG.debug("<== PathResourceMatcher.isMatch(resourceValue=" + resourceValue + ", expandedValue=" + expandedValue + ") : result:[" + ret + "]");
+				LOG.debug("<== PathResourceMatcher.isMatch(resourceValue={}, expandedValue={}): ret={}", resourceValue, getExpandedValue(evalContext) , ret );
 			}
 
 			return ret;
 		}
 
 		@Override
+		public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("==> PathResourceMatcher.isPrefixMatch(resourceValue={}, evalContext={})", resourceValue, evalContext);
+			}
+
+			boolean ret = isCaseSensitive ? StringUtils.startsWith(getExpandedValue(evalContext), resourceValue)
+			                              : StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue);
+
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("<== PathResourceMatcher.isPrefixMatch(resourceValue={}, expandedValue={}): ret={}", resourceValue, getExpandedValue(evalContext) , ret );
+			}
+
+			return ret;
+
+		}
+
+		@Override
 		public boolean isChildMatch(String resourceValue, Map<String, Object> evalContext) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("==> PathResourceMatcher.isChildMatch(resourceValue={}, evalContext={})", resourceValue, evalContext);
+			}
+
 			boolean ret                     = false;
 			String  expandedValue           = getExpandedValue(evalContext);
 			int     lastLevelSeparatorIndex = expandedValue.lastIndexOf(pathSeparatorChar);
@@ -357,9 +372,143 @@ public boolean isChildMatch(String resourceValue, Map<String, Object> evalContex
 				ret = function.apply(resourceValue, shorterExpandedValue);
 			}
 
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("<== PathResourceMatcher.isChildMatch(resourceValue={}, lastLevelSeparatorIndex={}): ret={}", resourceValue, lastLevelSeparatorIndex, ret );
+			}
+
+			return ret;
+		}
+	}
+
+	static class PathStartsWithResourceMatcher extends AbstractPathResourceMatcher {
+		PathStartsWithResourceMatcher(String value, Map<String, String> options, char pathSeparatorChar, boolean isCaseSensitive, int priority) {
+			super(value, options, pathSeparatorChar, isCaseSensitive, priority);
+		}
+
+		@Override
+		boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("==> PathStartsWithResourceMatcher.isMatch(resourceValue={}, evalContext={})", resourceValue, evalContext);
+			}
+
+			boolean ret = isCaseSensitive ? StringUtils.startsWith(resourceValue, getExpandedValue(evalContext))
+			                              : StringUtils.startsWithIgnoreCase(resourceValue, getExpandedValue(evalContext));
+
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("<== PathStartsWithResourceMatcher.isMatch(resourceValue={}, expandedValue={}): ret={}", resourceValue, getExpandedValue(evalContext) , ret );
+			}
+
 			return ret;
 		}
 
+		@Override
+		public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("==> PathStartsWithResourceMatcher.isPrefixMatch(resourceValue={}, evalContext={})", resourceValue, evalContext);
+			}
+
+			boolean ret = isCaseSensitive ? StringUtils.startsWith(getExpandedValue(evalContext), resourceValue)
+			                              : StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue);
+
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("<== PathStartsWithResourceMatcher.isPrefixMatch(resourceValue={}, expandedValue={}): ret={}", resourceValue, getExpandedValue(evalContext) , ret );
+			}
+
+			return ret;
+		}
+
+		@Override
+		public boolean isChildMatch(String resourceValue, Map<String, Object> evalContext) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("==> PathStartsWithResourceMatcher.isChildMatch(resourceValue={}, evalContext={})", resourceValue, evalContext);
+			}
+
+			boolean ret                     = false;
+			String  expandedValue           = getExpandedValue(evalContext);
+			int     lastLevelSeparatorIndex = expandedValue.lastIndexOf(pathSeparatorChar);
+
+			if (lastLevelSeparatorIndex != -1) {
+				String shorterExpandedValue = expandedValue.substring(0, lastLevelSeparatorIndex);
+
+				if (resourceValue.charAt(resourceValue.length()-1) == pathSeparatorChar) {
+					resourceValue = resourceValue.substring(0, resourceValue.length()-1);
+				}
+
+				ret = isCaseSensitive ? StringUtils.startsWith(resourceValue, shorterExpandedValue)
+				                      : StringUtils.startsWithIgnoreCase(resourceValue, shorterExpandedValue);
+			}
+
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("<== PathStartsWithResourceMatcher.isChildMatch(resourceValue={}, lastLevelSeparatorIndex={}): ret={}", resourceValue, lastLevelSeparatorIndex, ret );
+			}
+
+			return ret;
+		}
+	}
+
+	static class PathEndsWithResourceMatcher extends AbstractPathResourceMatcher {
+		PathEndsWithResourceMatcher(String value, Map<String, String> options, char pathSeparatorChar, boolean isCaseSensitive, int priority) {
+			super(value, options, pathSeparatorChar, isCaseSensitive, priority);
+		}
+
+		@Override
+		boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("==> PathEndsWithResourceMatcher.isMatch(resourceValue={}, evalContext={})", resourceValue, evalContext);
+			}
+
+			boolean ret = isCaseSensitive ? StringUtils.endsWith(resourceValue, getExpandedValue(evalContext))
+			                              : StringUtils.endsWithIgnoreCase(resourceValue, getExpandedValue(evalContext));
+
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("<== PathEndsWithResourceMatcher.isMatch(resourceValue={}, expandedValue={}): ret={}", resourceValue, getExpandedValue(evalContext) , ret );
+			}
+
+			return ret;
+		}
+
+		@Override
+		public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("==> PathEndsWithResourceMatcher.isPrefixMatch(resourceValue={}, evalContext={})", resourceValue, evalContext);
+			}
+
+			boolean ret = true; // isPrefixMatch() is always true for endsWith
+
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("<== PathEndsWithResourceMatcher.isPrefixMatch(resourceValue={}, expandedValue={}): ret={}", resourceValue, getExpandedValue(evalContext) , ret );
+			}
+
+			return ret;
+		}
+
+		@Override
+		public boolean isChildMatch(String resourceValue, Map<String, Object> evalContext) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("==> PathEndsWithResourceMatcher.isChildMatch(resourceValue={}, evalContext={})", resourceValue, evalContext);
+			}
+
+			boolean ret                     = false;
+			String  expandedValue           = getExpandedValue(evalContext);
+			int     lastLevelSeparatorIndex = expandedValue.lastIndexOf(pathSeparatorChar);
+
+			if (lastLevelSeparatorIndex != -1) {
+				String shorterExpandedValue = expandedValue.substring(0, lastLevelSeparatorIndex);
+
+				if (resourceValue.charAt(resourceValue.length()-1) == pathSeparatorChar) {
+					resourceValue = resourceValue.substring(0, resourceValue.length()-1);
+				}
+
+				ret = isCaseSensitive ? StringUtils.endsWith(resourceValue, shorterExpandedValue)
+				                      : StringUtils.endsWithIgnoreCase(resourceValue, shorterExpandedValue);
+			}
+
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("<== PathEndsWithResourceMatcher.isChildMatch(resourceValue={}, lastLevelSeparatorIndex={}): ret={}", resourceValue, lastLevelSeparatorIndex, ret );
+			}
+
+			return ret;
+		}
 	}
 
 	static class WildcardResourceMatcher extends AbstractPathResourceMatcher {
@@ -367,14 +516,14 @@ static class WildcardResourceMatcher extends AbstractPathResourceMatcher {
 		final IOCase                                       ioCase;
 
 		WildcardResourceMatcher(String value, Map<String, String> options, char pathSeparatorChar, boolean optIgnoreCase, TriFunction<String, String, IOCase, Boolean> function, int priority) {
-			super(value, options, pathSeparatorChar, priority, !optIgnoreCase);
+			super(value, options, pathSeparatorChar, !optIgnoreCase, priority);
 
 			this.function = function;
 			this.ioCase   = optIgnoreCase ? IOCase.INSENSITIVE : IOCase.SENSITIVE;
 		}
 
 		@Override
-		boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
+		public boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 			if (LOG.isDebugEnabled()) {
 				LOG.debug("==> WildcardResourceMatcher.isMatch(resourceValue=" + resourceValue + ", evalContext=" + evalContext + ")");
 			}
@@ -389,6 +538,20 @@ boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		}
 
 		@Override
+		public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("==> WildcardResourceMatcher.isPrefixMatch(resourceValue=" + resourceValue + ", evalContext=" + evalContext + ")");
+			}
+
+			boolean ret = ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), ioCase);
+
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("<== WildcardResourceMatcher.isPrefixMatch(resourceValue=" + resourceValue + ", expandedValue=" + getExpandedValue(evalContext) + ") : result:[" + ret + "]");
+			}
+			return ret;
+		}
+
+		@Override
 		public boolean isChildMatch(String resourceValue, Map<String, Object> evalContext) {
 			boolean ret                     = false;
 			String  expandedValue           = getExpandedValue(evalContext);
@@ -414,7 +577,7 @@ static class RecursiveWildcardResourceMatcher extends AbstractPathResourceMatche
 		String[] wildcardPathElements;
 
 		RecursiveWildcardResourceMatcher(String value, Map<String, String> options, char pathSeparatorChar, boolean optIgnoreCase, QuintFunction<String, String, Character, IOCase, Boolean, String[]> function, int priority) {
-			super(value, options, pathSeparatorChar, priority, !optIgnoreCase);
+			super(value, options, pathSeparatorChar, !optIgnoreCase, priority);
 
 			this.function = function;
 			this.ioCase   = optIgnoreCase ? IOCase.INSENSITIVE : IOCase.SENSITIVE;
@@ -445,6 +608,20 @@ boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		}
 
 		@Override
+		public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("==> RecursiveWildcardResourceMatcher.isPrefixMatch(resourceValue=" + resourceValue + ", evalContext=" + evalContext + ")");
+			}
+
+			boolean ret = ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), ioCase);
+
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("<== RecursiveWildcardResourceMatcher.isPrefixMatch(resourceValue=" + resourceValue + ", expandedValue=" + getExpandedValue(evalContext) + ") : result:[" + ret + "]");
+			}
+			return ret;
+		}
+
+		@Override
 		public boolean isChildMatch(String resourceValue, Map<String, Object> evalContext) {
 			boolean ret = false;
 			String expandedValue = getExpandedValue(evalContext);
@@ -470,14 +647,16 @@ static class RecursivePathResourceMatcher extends AbstractPathResourceMatcher {
 		String valueWithoutSeparator;
 		String valueWithSeparator;
 
+		final IOCase                              ioCase;
 		final BiFunction<String, String, Boolean> primaryFunction;
 		final BiFunction<String, String, Boolean> fallbackFunction;
 
-		RecursivePathResourceMatcher(String value, Map<String, String> options, char pathSeparatorChar, BiFunction<String, String, Boolean> primaryFunction, BiFunction<String, String, Boolean> fallbackFunction, int priority) {
-			super(value, options, pathSeparatorChar, priority, true);
+		RecursivePathResourceMatcher(String value, Map<String, String> options, char pathSeparatorChar, boolean optIgnoreCase, int priority) {
+			super(value, options, pathSeparatorChar, true, priority);
 
-			this.primaryFunction    = primaryFunction;
-			this.fallbackFunction   = fallbackFunction;
+			this.ioCase           = optIgnoreCase ? IOCase.INSENSITIVE : IOCase.SENSITIVE;
+			this.primaryFunction  = optIgnoreCase ? StringUtils::equalsIgnoreCase : StringUtils::equals;
+			this.fallbackFunction = optIgnoreCase ? StringUtils::startsWithIgnoreCase : StringUtils::startsWith;
 		}
 
 		String getStringToCompare(String policyValue) {
@@ -521,6 +700,20 @@ boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 		}
 
 		@Override
+		public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("==> RecursiveWildcardResourceMatcher.isPrefixMatch(resourceValue=" + resourceValue + ", evalContext=" + evalContext + ")");
+			}
+
+			boolean ret = ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), ioCase);
+
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("<== RecursiveWildcardResourceMatcher.isPrefixMatch(resourceValue=" + resourceValue + ", expandedValue=" + getExpandedValue(evalContext) + ") : result:[" + ret + "]");
+			}
+			return ret;
+		}
+
+		@Override
 		public boolean isChildMatch(String resourceValue, Map<String, Object> evalContext) {
 			boolean ret = false;
 			final String noSeparator;
@@ -534,7 +727,7 @@ public boolean isChildMatch(String resourceValue, Map<String, Object> evalContex
 				}
 				noSeparator = valueWithoutSeparator;
 			}
-			final int lastLevelSeparatorIndex = noSeparator.lastIndexOf(pathSeparatorChar);
+			final int lastLevelSeparatorIndex = noSeparator != null ? noSeparator.lastIndexOf(pathSeparatorChar) : -1;
 
 			if (lastLevelSeparatorIndex != -1) {
 				final String shorterExpandedValue = noSeparator.substring(0, lastLevelSeparatorIndex);
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerResourceMatcher.java b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerResourceMatcher.java
index c4ce30d..ec22e01 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerResourceMatcher.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerResourceMatcher.java
@@ -22,6 +22,7 @@
 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.policyengine.RangerAccessRequest.ResourceElementMatchType;
 
 import java.util.Map;
 
@@ -34,6 +35,8 @@ public interface RangerResourceMatcher {
 
 	boolean isMatchAny();
 
+	ResourceElementMatchType getMatchType(Object resource, ResourceElementMatchingScope matchingScope, Map<String, Object> evalContext);
+
 	boolean isMatch(Object resource, ResourceElementMatchingScope matchingScope, Map<String, Object> evalContext);
 
 	boolean isCompleteMatch(String resource, Map<String, Object> evalContext);
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcher.java b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcher.java
index ce73b30..0575636 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcher.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcher.java
@@ -237,7 +237,7 @@ static String getPathWithOutScheme(String url) {
 final class CaseSensitiveURLRecursiveWildcardMatcher extends AbstractStringResourceMatcher {
     private final char levelSeparatorChar;
     CaseSensitiveURLRecursiveWildcardMatcher(String value, Map<String, String> options, char levelSeparatorChar) {
-        super(value, options, true);
+        super(value, options);
         this.levelSeparatorChar = levelSeparatorChar;
     }
 
@@ -245,13 +245,19 @@ final class CaseSensitiveURLRecursiveWildcardMatcher extends AbstractStringResou
     boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
         return RangerURLResourceMatcher.isRecursiveWildCardMatch(resourceValue, getExpandedValue(evalContext), levelSeparatorChar, IOCase.SENSITIVE);
     }
+
+    @Override
+    public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+        return ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), IOCase.SENSITIVE);
+    }
+
     int getPriority() { return 7 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);}
 }
 
 final class CaseInsensitiveURLRecursiveWildcardMatcher extends AbstractStringResourceMatcher {
     private final char levelSeparatorChar;
     CaseInsensitiveURLRecursiveWildcardMatcher(String value, Map<String, String> options, char levelSeparatorChar) {
-        super(value, options, false);
+        super(value, options);
         this.levelSeparatorChar = levelSeparatorChar;
     }
 
@@ -259,6 +265,12 @@ final class CaseInsensitiveURLRecursiveWildcardMatcher extends AbstractStringRes
     boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
         return RangerURLResourceMatcher.isRecursiveWildCardMatch(resourceValue, getExpandedValue(evalContext), levelSeparatorChar, IOCase.INSENSITIVE);
     }
+
+    @Override
+    public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+        return ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), IOCase.INSENSITIVE);
+    }
+
     int getPriority() { return 8 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);}
 
 }
@@ -268,8 +280,8 @@ abstract class RecursiveMatcher extends AbstractStringResourceMatcher {
     String valueWithoutSeparator;
     String valueWithSeparator;
 
-    RecursiveMatcher(String value, Map<String, String> options, char levelSeparatorChar, boolean isCaseSensitive) {
-        super(value, options, isCaseSensitive);
+    RecursiveMatcher(String value, Map<String, String> options, char levelSeparatorChar) {
+        super(value, options);
         this.levelSeparatorChar = levelSeparatorChar;
     }
 
@@ -284,7 +296,7 @@ String getStringToCompare(String policyValue) {
 
 final class CaseSensitiveURLRecursiveMatcher extends RecursiveMatcher {
     CaseSensitiveURLRecursiveMatcher(String value, Map<String, String> options, char levelSeparatorChar) {
-        super(value, options, levelSeparatorChar, true);
+        super(value, options, levelSeparatorChar);
     }
 
     @Override
@@ -311,12 +323,18 @@ boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
 
         return ret;
     }
+
+    @Override
+    public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+        return StringUtils.startsWith(getExpandedValue(evalContext), resourceValue);
+    }
+
     int getPriority() { return 7 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);}
 }
 
 final class CaseInsensitiveURLRecursiveMatcher extends RecursiveMatcher {
     CaseInsensitiveURLRecursiveMatcher(String value, Map<String, String> options, char levelSeparatorChar) {
-        super(value, options, levelSeparatorChar, false);
+        super(value, options, levelSeparatorChar);
     }
 
     @Override
@@ -344,5 +362,10 @@ boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
         return ret;
     }
 
+    @Override
+    public boolean isPrefixMatch(String resourceValue, Map<String, Object> evalContext) {
+        return StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue);
+    }
+
     int getPriority() { return 8 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);}
 }
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/ResourceMatcher.java b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/ResourceMatcher.java
index ad0942f..77245ea 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/ResourceMatcher.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/ResourceMatcher.java
@@ -19,6 +19,7 @@
 
 package org.apache.ranger.plugin.resourcematcher;
 
+import org.apache.commons.io.IOCase;
 import org.apache.commons.lang.StringUtils;
 import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
 import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType;
@@ -30,8 +31,12 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Map;
+import java.util.Stack;
 
 abstract class ResourceMatcher {
     private static final Logger LOG = LoggerFactory.getLogger(ResourceMatcher.class);
@@ -60,19 +65,8 @@ abstract class ResourceMatcher {
 
     final boolean isMatch(String resourceValue, ResourceElementMatchingScope matchingScope, Map<String, Object> evalContext) {
         final ResourceElementMatchType matchType = getMatchType(resourceValue, matchingScope, evalContext);
-        final boolean                  ret;
 
-        if (matchType == ResourceElementMatchType.SELF) {
-            ret = true;
-        } else if (matchType == ResourceElementMatchType.PREFIX) {
-            ret = matchingScope == ResourceElementMatchingScope.SELF_OR_PREFIX;
-        } else if (matchType == ResourceElementMatchType.CHILD) {
-            ret = matchingScope == ResourceElementMatchingScope.SELF_OR_CHILD;
-        } else {
-            ret = false;
-        }
-
-        return ret;
+        return isMatch(matchType, matchingScope);
     }
 
     final ResourceElementMatchType getMatchType(String resourceValue, ResourceElementMatchingScope matchingScope, Map<String, Object> evalContext) {
@@ -152,6 +146,153 @@ public static boolean startsWithAnyChar(String value, String startChars) {
         return ret;
     }
 
+    public static boolean isMatch(ResourceElementMatchType matchType, ResourceElementMatchingScope matchingScope) {
+        final boolean ret;
+
+        switch (matchType) {
+            case SELF:
+                ret = true;
+                break;
+
+            case CHILD:
+                ret = matchingScope == ResourceElementMatchingScope.SELF_OR_CHILD;
+                break;
+
+            case PREFIX:
+                ret = matchingScope == ResourceElementMatchingScope.SELF_OR_PREFIX;
+                break;
+
+            default:
+                LOG.error("invalid ResourceElementMatchType: {}}", matchType);
+
+                ret = false;
+        }
+
+        return ret;
+    }
+
+    // modified version of FilenameUtils.wildcardMatch(), to check if value is a prefix match for wildcardMatcher
+    public static boolean wildcardPrefixMatch(String value, String wildcardMatcher, IOCase caseSensitivity) {
+        if (value == null && wildcardMatcher == null) {
+            return true;
+        } else if (value == null || wildcardMatcher == null) {
+            return false;
+        }
+
+        if (caseSensitivity == null) {
+            caseSensitivity = IOCase.SENSITIVE;
+        }
+
+        List<String> wcsTokens = splitOnTokens(wildcardMatcher);
+        boolean      anyChars  = false;
+        int          textIdx   = 0;
+        int          wcsIdx    = 0;
+        Stack<int[]> backtrack = new Stack<>();
+
+        do {
+            if (backtrack.size() > 0) {
+                int[] array = backtrack.pop();
+
+                wcsIdx   = array[0];
+                textIdx  = array[1];
+                anyChars = true;
+            }
+
+            for(; wcsIdx < wcsTokens.size(); ++wcsIdx) {
+                String wcsToken = wcsTokens.get(wcsIdx);
+
+                if (wcsToken.equals("?")) {
+                    ++textIdx;
+
+                    if (textIdx > value.length()) {
+                        break;
+                    }
+
+                    anyChars = false;
+                } else if (wcsToken.equals("*")) {
+                    anyChars = true;
+
+                    if (wcsIdx == wcsTokens.size() - 1) {
+                        textIdx = value.length();
+                    }
+                } else {
+                    // changes from FilenameUtils.wildcardMatch(): added following 3 lines to check if value is a prefix match for wildcardMatcher
+                    if (wcsToken.length() > (value.length() - textIdx)) {
+                        wcsToken = wcsToken.substring(0, value.length() - textIdx);
+                    }
+
+                    if (anyChars) {
+                        textIdx = caseSensitivity.checkIndexOf(value, textIdx, wcsToken);
+
+                        if (textIdx == -1) {
+                            break;
+                        }
+
+                        int repeat = caseSensitivity.checkIndexOf(value, textIdx + 1, wcsToken);
+
+                        if (repeat >= 0) {
+                            backtrack.push(new int[]{wcsIdx, repeat});
+                        }
+                    } else if (!caseSensitivity.checkRegionMatches(value, textIdx, wcsToken)) {
+                        break;
+                    }
+
+                    textIdx += wcsToken.length();
+
+                    anyChars = false;
+                }
+            }
+
+            // changes from FilenameUtils.wildcardMatch(): replaced the condition in 'if' below to check if value is a prefix match for wildcardMatcher
+            //   original if: if (wcsIdx == wcsTokens.size() && textIdx == value.length())
+            if (wcsIdx == wcsTokens.size() || textIdx == value.length()) {
+                return true;
+            }
+        } while (backtrack.size() > 0);
+
+        return anyChars;
+    }
+
+    static List<String> splitOnTokens(String text) {
+        if (text.indexOf(63) == -1 && text.indexOf(42) == -1) {
+            return Collections.singletonList(text);
+        } else {
+            char[]        array    = text.toCharArray();
+            List<String>  list     = new ArrayList<>(2);
+            StringBuilder buffer   = new StringBuilder();
+            char          prevChar = 0;
+            char[]        arr$     = array;
+            int           len$     = array.length;
+
+            for(int i$ = 0; i$ < len$; ++i$) {
+                char ch = arr$[i$];
+
+                if (ch != '?' && ch != '*') {
+                    buffer.append(ch);
+                } else {
+                    if (buffer.length() != 0) {
+                        list.add(buffer.toString());
+                        buffer.setLength(0);
+                    }
+
+                    if (ch == '?') {
+                        list.add("?");
+                    } else if (prevChar != '*') {
+                        list.add("*");
+                    }
+                }
+
+                prevChar = ch;
+            }
+
+            if (buffer.length() != 0) {
+                list.add(buffer.toString());
+            }
+
+            return list;
+        }
+    }
+
     public static class PriorityComparator implements Comparator<ResourceMatcher>, Serializable {
         @Override
         public int compare(ResourceMatcher me, ResourceMatcher other) {
diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcherTest.java b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcherTest.java
index 767795e..b686418 100644
--- a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcherTest.java
+++ b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcherTest.java
@@ -19,7 +19,9 @@
 
 package org.apache.ranger.plugin.resourcematcher;
 
+import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
 import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchingScope;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType;
 import org.junit.Test;
 
 import java.util.Map;
@@ -47,5 +49,12 @@ public boolean isMatch(Object resource, ResourceElementMatchingScope matchingSco
             fail("This method is not expected to be used by test!");
             return false;
         }
+
+        @Override
+        public ResourceElementMatchType getMatchType(Object resource, ResourceElementMatchingScope matchingScope, Map<String, Object> evalContext) {
+            fail("This method is not expected to be used by test!");
+            return RangerAccessRequest.ResourceElementMatchType.NONE;
+        }
+
     }
 }
diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcherTest.java b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcherTest.java
index e00bc83..8a297bd 100644
--- a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcherTest.java
+++ b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcherTest.java
@@ -23,6 +23,7 @@
 import org.apache.ranger.plugin.model.RangerPolicy;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef;
 import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchingScope;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType;
 import org.apache.ranger.plugin.util.RangerAccessRequestUtil;
 import org.junit.Test;
 
@@ -30,43 +31,132 @@
 import java.util.Map;
 
 import static org.junit.Assert.*;
+import static org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType.*;
 
 public class RangerDefaultResourceMatcherTest {
 
     Object[][] data = {
-            // { resource, policy, excludes, result
-            { "*",  "*",  false, true, "user" },  // resource is all values
-            { "*",  "*",  true,  false, "user" },
-            { "*",  "a*", false, false, "user" }, // but, policy is not match any
-            { "*",  "a*", true,  false, "user" }, // ==> compare with above: exclude flag has no effect here
-            { "a*", "a",  false, false, "user" }, // resource has regex marker!
-            { "a*", "a",  true,  true, "user" },
-            { "a",  "a",  false, true, "user" },  // exact match
-            { "a",  "a",  true,  false, "user" },
-            { "a1", "a*", false, true, "user" },  // trivial regex match
-            { "a1", "a*", true,  false, "user" },
+         // { resource, policy, excludes, matchType, result, user }
+            { "*",  "*",  false, SELF, true,  "user" },  // resource is all values
+            { "*",  "*",  true,  NONE, false, "user" },
+            { "*",  "a*", false, NONE, false, "user" }, // but, policy is not match any
+            { "*",  "a*", true,  NONE, false, "user" }, // ==> compare with above: exclude flag has no effect here
+            { "a*", "a",  false, NONE, false, "user" }, // resource has regex marker!
+            { "a*", "a",  true,  SELF, true,  "user" },
+            { "a",  "a",  false, SELF, true,  "user" },  // exact match
+            { "a",  "a",  true,  NONE, false, "user" },
+            { "a1", "a*", false, SELF, true,  "user" },  // trivial regex match
+            { "a1", "a*", true,  NONE, false, "user" },
+
+            // matchScope=SELF, excludes=false
+            { "*",   "*",    false, SELF, true,  "user" }, // resource is all values
+            { "a*",  "*",    false, SELF, true,  "user" }, // resource has regex, policy matches
+            { "a*",  "a",    false, NONE, false, "user" }, // resource has regex, policy does not match
+            { "*",   "a*",   false, NONE, false, "user" }, // resource has regex, policy does not match
+            { "a*",  "a*",   false, SELF, true,  "user" }, // resource has regex, policy matches
+            { "a?",  "a*",   false, SELF, true,  "user" }, // resource has regex, policy matches
+            { "a*b", "a*",   false, SELF, true,  "user" }, // resource has regex, policy matches
+            { "a?b", "a*",   false, SELF, true,  "user" }, // resource has regex, policy matches
+            { "a*b", "a*b",  false, SELF, true,  "user" }, // resource has regex, policy matches
+            { "a?b", "a?b",  false, SELF, true,  "user" }, // resource has regex, policy matches
+            { "a1b", "a1b",  false, SELF, true,  "user" }, // exact match
+            { "a1b", "a*",   false, SELF, true,  "user" }, // regex match - suffix
+            { "a1b", "*b",   false, SELF, true,  "user" }, // regex match - prefix
+            { "a1b", "*1*",  false, SELF, true,  "user" }, // regex match
+            { "a1b", "a?b",  false, SELF, true,  "user" }, // regex match - single char
+            { "a",   "abc",  false, NONE, false, "user" }, // policy has more than resource
+            { "ab",  "abc",  false, NONE, false, "user" }, // policy has more than resource
+            { "ab",  "*c",   false, NONE, false, "user" }, // policy has more than resource
+            { "*b",  "a*bc", false, NONE, false, "user" }, // policy has more than resource
+            { "a*b", "a*bc", false, NONE, false, "user" }, // policy has more than resource
+
+            // matchScope=SELF, excludes=true
+            { "*",   "*",    true, NONE, false, "user" },
+            { "a*",  "*",    true, NONE, false, "user" },
+            { "a*",  "a",    true, SELF, true,  "user" },
+            { "*",   "a*",   true, NONE, false, "user" },  // ==> compare with above: exclude flag has no effect here
+            { "a*",  "a*",   true, NONE, false, "user" },
+            { "a?",  "a*",   true, NONE, false, "user" },
+            { "a*b", "a*",   true, NONE, false, "user" },
+            { "a?b", "a*",   true, NONE, false, "user" },
+            { "a*b", "a*b",  true, NONE, false, "user" },
+            { "a?b", "a?b",  true, NONE, false, "user" },
+            { "a1b", "a1b",  true, NONE, false, "user" },
+            { "a1b", "a*",   true, NONE, false, "user" },
+            { "a1b", "*b",   true, NONE, false, "user" },
+            { "a1b", "*1*",  true, NONE, false, "user" },
+            { "a1b", "a?b",  true, NONE, false, "user" },
+            { "a",   "abc",  true, SELF, true,  "user" },
+            { "ab",  "abc",  true, SELF, true,  "user" },
+            { "ab",  "*c",   true, SELF, true,  "user" },
+            { "*b",  "a*bc", true, SELF, true,  "user" },
+            { "a*b", "a*bc", true, SELF, true,  "user" },
+    };
+
+    Object[][] dataForPrefixMatch = {
+            // { resource, policy, excludes, matchType, result, user }
+            { "a",    "abc",    false, PREFIX, true,  "user" },
+            { "ab",   "abc",    false, PREFIX, true,  "user" },
+            { "ab",   "*c",     false, PREFIX, true,  "user" },
+            { "a",    "a*c",    false, PREFIX, true,  "user" },
+            { "ab",   "a*c",    false, PREFIX, true,  "user" },
+            { "abc",  "a*c",    false, SELF,   true,  "user" },
+            { "abcd", "a*c",    false, PREFIX, true,  "user" },
+            { "abcd", "a*c*d",  false, SELF,   true,  "user" },
+            { "abcd", "a*c*de", false, PREFIX, true,  "user" },
+            { "acbd", "ab*c",   false, NONE,   false, "user" },
+            { "b",    "ab*c",   false, NONE,   false, "user" },
+            { "a",    "ab*",    false, PREFIX, true,  "user" },
+            { "b",    "ab*",    false, NONE,   false, "user" },
     };
 
     @Test
     public void testIsMatch() throws Exception {
-        for (Object[] row : data) {
-            String resource = (String)row[0];
-            String policyValue = (String)row[1];
-            boolean excludes = (boolean)row[2];
-            boolean result = (boolean)row[3];
-            String user = (String) row[4];
+        ResourceElementMatchingScope matchScope = ResourceElementMatchingScope.SELF;
 
-            Map<String, Object> evalContext = new HashMap<>();
+        for (Object[] row : data) {
+            String                   resource    = (String)row[0];
+            String                   policyValue = (String)row[1];
+            boolean                  excludes    = (boolean)row[2];
+            ResourceElementMatchType matchType   = (ResourceElementMatchType) row[3];
+            boolean                  result      = (boolean)row[4];
+            String                   user        = (String) row[5];
+            Map<String, Object>      evalContext = new HashMap<>();
+
             RangerAccessRequestUtil.setCurrentUserInContext(evalContext, user);
 
             MatcherWrapper matcher = new MatcherWrapper(policyValue, excludes);
-            assertEquals(getMessage(row), result, matcher.isMatch(resource, ResourceElementMatchingScope.SELF, evalContext));
+
+            assertEquals(getMessage(row), matchType, matcher.getMatchType(resource, matchScope, evalContext));
+            assertEquals(getMessage(row), result, matcher.isMatch(resource, matchScope, evalContext));
+        }
+    }
+
+    @Test
+    public void testIsPrefixMatch() {
+        ResourceElementMatchingScope matchScope = ResourceElementMatchingScope.SELF_OR_PREFIX;
+
+        for (Object[] row : dataForPrefixMatch) {
+            String                   resource    = (String)row[0];
+            String                   policyValue = (String)row[1];
+            boolean                  excludes    = (boolean)row[2];
+            ResourceElementMatchType matchType   = (ResourceElementMatchType) row[3];
+            boolean                  result      = (boolean)row[4];
+            String                   user        = (String) row[5];
+            Map<String, Object>      evalContext = new HashMap<>();
+
+            RangerAccessRequestUtil.setCurrentUserInContext(evalContext, user);
+
+            MatcherWrapper matcher = new MatcherWrapper(policyValue, excludes);
+
+            assertEquals(getMessage(row), matchType, matcher.getMatchType(resource, matchScope, evalContext));
+            assertEquals(getMessage(row), result, matcher.isMatch(resource, matchScope, evalContext));
         }
     }
 
     String getMessage(Object[] row) {
-        return String.format("Resource=%s, Policy=%s, excludes=%s, result=%s",
-                (String)row[0], (String)row[1], (boolean)row[2], (boolean)row[3]);
+        return String.format("Resource=%s, Policy=%s, excludes=%s, matchScope=%s, matchType=%s, result=%s",
+                row[0], row[1], row[2], row[3], row[4], row[5]);
     }
 
     static class MatcherWrapper extends RangerDefaultResourceMatcher {
@@ -74,7 +164,7 @@ static class MatcherWrapper extends RangerDefaultResourceMatcher {
             RangerResourceDef   resourceDef    = new RangerResourceDef();
             Map<String, String> matcherOptions = new HashMap<>();
 
-            matcherOptions.put(OPTION_WILD_CARD, Boolean.toString(policyValue.contains(WILDCARD_ASTERISK)));
+            matcherOptions.put(OPTION_WILD_CARD, Boolean.toString(policyValue.contains(WILDCARD_ASTERISK) || policyValue.contains(WILDCARD_QUESTION_MARK)));
             matcherOptions.put(OPTION_IGNORE_CASE, Boolean.toString(false));
 
             resourceDef.setMatcherOptions(matcherOptions);
diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcherTest.java b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcherTest.java
index ed02be6..3727b30 100644
--- a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcherTest.java
+++ b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcherTest.java
@@ -94,6 +94,59 @@ public class RangerPathResourceMatcherTest {
             { "/app/hbase/test.db",   "/app/hbase/test.db/tmp/test.t*",  true, false, false, "user" },
     };
 
+    Object[][] dataForSelfOrPrefixScope = {
+            // { resource, policy, optWildcard, recursive, result
+            { "/",                 "/app/hive/test.db", true, false, true, "user" },
+            { "/app",              "/app/hive/test.db", true, false, true, "user" },
+            { "/app/",             "/app/hive/test.db", true, false, true, "user" },
+            { "/app/hive",         "/app/hive/test.db", true, false, true, "user" },
+            { "/app/hive/",        "/app/hive/test.db", true, false, true, "user" },
+            { "/app/hive/test.db", "/app/hive/test.db", true, false, true, "user" },
+            { "/",                 "/app/*/test.db",    true, false, true, "user" },
+            { "/app",              "/app/*/test.db",    true, false, true, "user" },
+            { "/app/",             "/app/*/test.db",    true, false, true, "user" },
+            { "/app/hive",         "/app/*/test.db",    true, false, true, "user" },
+            { "/app/hive/",        "/app/*/test.db",    true, false, true, "user" },
+            { "/app/hive/test.db", "/app/*/test.db",    true, false, true, "user" },
+            { "/",                 "*/hive/test.db",    true, false, true, "user" },
+            { "/app",              "*/hive/test.db",    true, false, true, "user" },
+            { "/app/",             "*/hive/test.db",    true, false, true, "user" },
+            { "/app/hive",         "*/hive/test.db",    true, false, true, "user" },
+            { "/app/hive/",        "*/hive/test.db",    true, false, true, "user" },
+            { "/app/hive/test.db", "*/hive/test.db",    true, false, true, "user" },
+            { "/",                 "/*",                true, false, true, "user" },
+            { "/app",              "/*",                true, false, true, "user" },
+            { "/app/",             "/*",                true, false, true, "user" },
+            { "/app/hive",         "/*",                true, false, true, "user" },
+            { "/app/hive/",        "/*",                true, false, true, "user" },
+            { "/app/hive/test.db", "/*",                true, false, true, "user" },
+
+            { "/",                 "/app/hive/test.db", true, true, true, "user" },
+            { "/app",              "/app/hive/test.db", true, true, true, "user" },
+            { "/app/",             "/app/hive/test.db", true, true, true, "user" },
+            { "/app/hive",         "/app/hive/test.db", true, true, true, "user" },
+            { "/app/hive/",        "/app/hive/test.db", true, true, true, "user" },
+            { "/app/hive/test.db", "/app/hive/test.db", true, true, true, "user" },
+            { "/",                 "/app/*/test.db",    true, true, true, "user" },
+            { "/app",              "/app/*/test.db",    true, true, true, "user" },
+            { "/app/",             "/app/*/test.db",    true, true, true, "user" },
+            { "/app/hive",         "/app/*/test.db",    true, true, true, "user" },
+            { "/app/hive/",        "/app/*/test.db",    true, true, true, "user" },
+            { "/app/hive/test.db", "/app/*/test.db",    true, true, true, "user" },
+            { "/",                 "*/hive/test.db",    true, true, true, "user" },
+            { "/app",              "*/hive/test.db",    true, true, true, "user" },
+            { "/app/",             "*/hive/test.db",    true, true, true, "user" },
+            { "/app/hive",         "*/hive/test.db",    true, true, true, "user" },
+            { "/app/hive/",        "*/hive/test.db",    true, true, true, "user" },
+            { "/app/hive/test.db", "*/hive/test.db",    true, true, true, "user" },
+            { "/",                 "/",                 true, true, true, "user" },
+            { "/app",              "/",                 true, true, true, "user" },
+            { "/app/",             "/",                 true, true, true, "user" },
+            { "/app/hive",         "/",                 true, true, true, "user" },
+            { "/app/hive/",        "/",                 true, true, true, "user" },
+            { "/app/hive/test.db", "/",                 true, true, true, "user" },
+    };
+
     @Test
     public void testIsMatch() throws Exception {
         for (Object[] row : data) {
@@ -130,6 +183,26 @@ public void testIsMatchForSelfOrChildScope() throws Exception {
         }
     }
 
+    @Test
+    public void testIsMatchForSelfOrPrefixScope() {
+        ResourceElementMatchingScope matchScope = ResourceElementMatchingScope.SELF_OR_PREFIX;
+
+        for (Object[] row : dataForSelfOrPrefixScope) {
+            String  resource    = (String)row[0];
+            String  policyValue = (String)row[1];
+            boolean optWildcard = (boolean)row[2];
+            boolean isRecursive = (boolean)row[3];
+            boolean result      = (boolean)row[4];
+            String  user        = (String) row[5];
+            Map<String, Object> evalContext = new HashMap<>();
+
+            RangerAccessRequestUtil.setCurrentUserInContext(evalContext, user);
+
+            MatcherWrapper matcher = new MatcherWrapper(policyValue, optWildcard, isRecursive);
+            assertEquals(getMessage(row), result, matcher.isMatch(resource, matchScope, evalContext));
+        }
+    }
+
     String getMessage(Object[] row) {
         return String.format("Resource=%s, Policy=%s, optWildcard=%s, recursive=%s, result=%s",
                 (String)row[0], (String)row[1], (boolean)row[2], (boolean)row[3], (boolean)row[4]);
diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcherTest.java b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcherTest.java
index 904cb61..cc73076 100644
--- a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcherTest.java
+++ b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcherTest.java
@@ -53,6 +53,60 @@ public class RangerURLResourceMatcherTest {
             { "://apps/warehouse/data/emp.db",                       "hdfs://app/*",              true,  true,  false, "user" }
     };
 
+
+    Object[][] dataForSelfOrPrefixScope = {
+            // { resource, policy, optWildcard, recursive, result
+            { "hdfs://hostname:8020/",                 "hdfs://hostname:8020/app/hive/test.db", true, false, true, "user" },
+            { "hdfs://hostname:8020/app",              "hdfs://hostname:8020/app/hive/test.db", true, false, true, "user" },
+            { "hdfs://hostname:8020/app/",             "hdfs://hostname:8020/app/hive/test.db", true, false, true, "user" },
+            { "hdfs://hostname:8020/app/hive",         "hdfs://hostname:8020/app/hive/test.db", true, false, true, "user" },
+            { "hdfs://hostname:8020/app/hive/",        "hdfs://hostname:8020/app/hive/test.db", true, false, true, "user" },
+            { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020/app/hive/test.db", true, false, true, "user" },
+            { "hdfs://hostname:8020/",                 "hdfs://hostname:8020/app/*/test.db",    true, false, true, "user" },
+            { "hdfs://hostname:8020/app",              "hdfs://hostname:8020/app/*/test.db",    true, false, true, "user" },
+            { "hdfs://hostname:8020/app/",             "hdfs://hostname:8020/app/*/test.db",    true, false, true, "user" },
+            { "hdfs://hostname:8020/app/hive",         "hdfs://hostname:8020/app/*/test.db",    true, false, true, "user" },
+            { "hdfs://hostname:8020/app/hive/",        "hdfs://hostname:8020/app/*/test.db",    true, false, true, "user" },
+            { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020/app/*/test.db",    true, false, true, "user" },
+            { "hdfs://hostname:8020/",                 "hdfs://hostname:8020*/hive/test.db",    true, false, true, "user" },
+            { "hdfs://hostname:8020/app",              "hdfs://hostname:8020*/hive/test.db",    true, false, true, "user" },
+            { "hdfs://hostname:8020/app/",             "hdfs://hostname:8020*/hive/test.db",    true, false, true, "user" },
+            { "hdfs://hostname:8020/app/hive",         "hdfs://hostname:8020*/hive/test.db",    true, false, true, "user" },
+            { "hdfs://hostname:8020/app/hive/",        "hdfs://hostname:8020*/hive/test.db",    true, false, true, "user" },
+            { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020*/hive/test.db",    true, false, true, "user" },
+            { "hdfs://hostname:8020/",                 "hdfs://hostname:8020/*",                true, false, true, "user" },
+            { "hdfs://hostname:8020/app",              "hdfs://hostname:8020/*",                true, false, true, "user" },
+            { "hdfs://hostname:8020/app/",             "hdfs://hostname:8020/*",                true, false, true, "user" },
+            { "hdfs://hostname:8020/app/hive",         "hdfs://hostname:8020/*",                true, false, true, "user" },
+            { "hdfs://hostname:8020/app/hive/",        "hdfs://hostname:8020/*",                true, false, true, "user" },
+            { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020/*",                true, false, true, "user" },
+
+            { "hdfs://hostname:8020/",                 "hdfs://hostname:8020/app/hive/test.db", true, true, true, "user" },
+            { "hdfs://hostname:8020/app",              "hdfs://hostname:8020/app/hive/test.db", true, true, true, "user" },
+            { "hdfs://hostname:8020/app/",             "hdfs://hostname:8020/app/hive/test.db", true, true, true, "user" },
+            { "hdfs://hostname:8020/app/hive",         "hdfs://hostname:8020/app/hive/test.db", true, true, true, "user" },
+            { "hdfs://hostname:8020/app/hive/",        "hdfs://hostname:8020/app/hive/test.db", true, true, true, "user" },
+            { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020/app/hive/test.db", true, true, true, "user" },
+            { "hdfs://hostname:8020/",                 "hdfs://hostname:8020/app/*/test.db",    true, true, true, "user" },
+            { "hdfs://hostname:8020/app",              "hdfs://hostname:8020/app/*/test.db",    true, true, true, "user" },
+            { "hdfs://hostname:8020/app/",             "hdfs://hostname:8020/app/*/test.db",    true, true, true, "user" },
+            { "hdfs://hostname:8020/app/hive",         "hdfs://hostname:8020/app/*/test.db",    true, true, true, "user" },
+            { "hdfs://hostname:8020/app/hive/",        "hdfs://hostname:8020/app/*/test.db",    true, true, true, "user" },
+            { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020/app/*/test.db",    true, true, true, "user" },
+            { "hdfs://hostname:8020/",                 "hdfs://hostname:8020*/hive/test.db",    true, true, true, "user" },
+            { "hdfs://hostname:8020/app",              "hdfs://hostname:8020*/hive/test.db",    true, true, true, "user" },
+            { "hdfs://hostname:8020/app/",             "hdfs://hostname:8020*/hive/test.db",    true, true, true, "user" },
+            { "hdfs://hostname:8020/app/hive",         "hdfs://hostname:8020*/hive/test.db",    true, true, true, "user" },
+            { "hdfs://hostname:8020/app/hive/",        "hdfs://hostname:8020*/hive/test.db",    true, true, true, "user" },
+            { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020*/hive/test.db",    true, true, true, "user" },
+            { "hdfs://hostname:8020/",                 "hdfs://hostname:8020/",                true, true, true, "user" },
+            { "hdfs://hostname:8020/app",              "hdfs://hostname:8020/",                true, true, true, "user" },
+            { "hdfs://hostname:8020/app/",             "hdfs://hostname:8020/",                true, true, true, "user" },
+            { "hdfs://hostname:8020/app/hive",         "hdfs://hostname:8020/",                true, true, true, "user" },
+            { "hdfs://hostname:8020/app/hive/",        "hdfs://hostname:8020/",                true, true, true, "user" },
+            { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020/",                true, true, true, "user" },
+    };
+
     @Test
     public void testIsMatch() throws Exception {
         for (Object[] row : data) {
@@ -71,6 +125,25 @@ public void testIsMatch() throws Exception {
         }
     }
 
+    @Test
+    public void testIsPrefixMatch() {
+        for (Object[] row : dataForSelfOrPrefixScope) {
+            String  resource    = (String)row[0];
+            String  policyValue = (String)row[1];
+            boolean optWildcard = (boolean)row[2];
+            boolean isRecursive = (boolean)row[3];
+            boolean result      = (boolean)row[4];
+            String  user        = (String) row[5];
+
+            Map<String, Object> evalContext = new HashMap<>();
+
+            RangerAccessRequestUtil.setCurrentUserInContext(evalContext, user);
+
+            MatcherWrapper matcher = new MatcherWrapper(policyValue, optWildcard, isRecursive);
+            assertEquals(getMessage(row), result, matcher.isMatch(resource, ResourceElementMatchingScope.SELF_OR_PREFIX, evalContext));
+        }
+    }
+
     String getMessage(Object[] row) {
         return String.format("Resource=%s, Policy=%s, optWildcard=%s, recursive=%s, result=%s",
                 (String)row[0], (String)row[1], (boolean)row[2], (boolean)row[3], (boolean)row[4]);