| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- package policy_engine
- import (
- "regexp"
- "strings"
- "sync"
- "github.com/seaweedfs/seaweedfs/weed/glog"
- )
- // WildcardMatcher provides unified wildcard matching functionality
- type WildcardMatcher struct {
- // Use regex for complex patterns with ? wildcards
- // Use string manipulation for simple * patterns (better performance)
- useRegex bool
- regex *regexp.Regexp
- pattern string
- }
- // WildcardMatcherCache provides caching for WildcardMatcher instances
- type WildcardMatcherCache struct {
- mu sync.RWMutex
- matchers map[string]*WildcardMatcher
- maxSize int
- accessOrder []string // For LRU eviction
- }
- // NewWildcardMatcherCache creates a new WildcardMatcherCache with a configurable maxSize
- func NewWildcardMatcherCache(maxSize int) *WildcardMatcherCache {
- if maxSize <= 0 {
- maxSize = 1000 // Default value
- }
- return &WildcardMatcherCache{
- matchers: make(map[string]*WildcardMatcher),
- maxSize: maxSize,
- }
- }
- // Global cache instance
- var wildcardMatcherCache = NewWildcardMatcherCache(1000) // Default maxSize
- // GetCachedWildcardMatcher gets or creates a cached WildcardMatcher for the given pattern
- func GetCachedWildcardMatcher(pattern string) (*WildcardMatcher, error) {
- // Fast path: check if already in cache
- wildcardMatcherCache.mu.RLock()
- if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists {
- wildcardMatcherCache.mu.RUnlock()
- wildcardMatcherCache.updateAccessOrder(pattern)
- return matcher, nil
- }
- wildcardMatcherCache.mu.RUnlock()
- // Slow path: create new matcher and cache it
- wildcardMatcherCache.mu.Lock()
- defer wildcardMatcherCache.mu.Unlock()
- // Double-check after acquiring write lock
- if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists {
- wildcardMatcherCache.updateAccessOrderLocked(pattern)
- return matcher, nil
- }
- // Create new matcher
- matcher, err := NewWildcardMatcher(pattern)
- if err != nil {
- return nil, err
- }
- // Evict old entries if cache is full
- if len(wildcardMatcherCache.matchers) >= wildcardMatcherCache.maxSize {
- wildcardMatcherCache.evictLeastRecentlyUsed()
- }
- // Cache it
- wildcardMatcherCache.matchers[pattern] = matcher
- wildcardMatcherCache.accessOrder = append(wildcardMatcherCache.accessOrder, pattern)
- return matcher, nil
- }
- // updateAccessOrder updates the access order for LRU eviction (with read lock)
- func (c *WildcardMatcherCache) updateAccessOrder(pattern string) {
- c.mu.Lock()
- defer c.mu.Unlock()
- c.updateAccessOrderLocked(pattern)
- }
- // updateAccessOrderLocked updates the access order for LRU eviction (without locking)
- func (c *WildcardMatcherCache) updateAccessOrderLocked(pattern string) {
- // Remove pattern from its current position
- for i, p := range c.accessOrder {
- if p == pattern {
- c.accessOrder = append(c.accessOrder[:i], c.accessOrder[i+1:]...)
- break
- }
- }
- // Add pattern to the end (most recently used)
- c.accessOrder = append(c.accessOrder, pattern)
- }
- // evictLeastRecentlyUsed removes the least recently used pattern from the cache
- func (c *WildcardMatcherCache) evictLeastRecentlyUsed() {
- if len(c.accessOrder) == 0 {
- return
- }
- // Remove the least recently used pattern (first in the list)
- lruPattern := c.accessOrder[0]
- c.accessOrder = c.accessOrder[1:]
- delete(c.matchers, lruPattern)
- }
- // ClearCache clears all cached patterns (useful for testing)
- func (c *WildcardMatcherCache) ClearCache() {
- c.mu.Lock()
- defer c.mu.Unlock()
- c.matchers = make(map[string]*WildcardMatcher)
- c.accessOrder = c.accessOrder[:0]
- }
- // GetCacheStats returns cache statistics
- func (c *WildcardMatcherCache) GetCacheStats() (size int, maxSize int) {
- c.mu.RLock()
- defer c.mu.RUnlock()
- return len(c.matchers), c.maxSize
- }
- // NewWildcardMatcher creates a new wildcard matcher for the given pattern
- func NewWildcardMatcher(pattern string) (*WildcardMatcher, error) {
- matcher := &WildcardMatcher{
- pattern: pattern,
- }
- // Determine if we need regex (contains ? wildcards)
- if strings.Contains(pattern, "?") {
- matcher.useRegex = true
- regex, err := compileWildcardPattern(pattern)
- if err != nil {
- return nil, err
- }
- matcher.regex = regex
- } else {
- matcher.useRegex = false
- }
- return matcher, nil
- }
- // Match checks if a string matches the wildcard pattern
- func (m *WildcardMatcher) Match(str string) bool {
- if m.useRegex {
- return m.regex.MatchString(str)
- }
- return matchWildcardString(m.pattern, str)
- }
- // MatchesWildcard provides a simple function interface for wildcard matching
- // This function consolidates the logic from the previous separate implementations
- func MatchesWildcard(pattern, str string) bool {
- // Handle simple cases first
- if pattern == "*" {
- return true
- }
- if pattern == str {
- return true
- }
- // Use regex for patterns with ? wildcards, string manipulation for * only
- if strings.Contains(pattern, "?") {
- return matchWildcardRegex(pattern, str)
- }
- return matchWildcardString(pattern, str)
- }
- // CompileWildcardPattern converts a wildcard pattern to a compiled regex
- // This replaces the previous compilePattern function
- func CompileWildcardPattern(pattern string) (*regexp.Regexp, error) {
- return compileWildcardPattern(pattern)
- }
- // matchWildcardString uses string manipulation for * wildcards only (more efficient)
- func matchWildcardString(pattern, str string) bool {
- // Handle simple cases
- if pattern == "*" {
- return true
- }
- if pattern == str {
- return true
- }
- // Split pattern by wildcards
- parts := strings.Split(pattern, "*")
- if len(parts) == 1 {
- // No wildcards, exact match
- return pattern == str
- }
- // Check if string starts with first part
- if len(parts[0]) > 0 && !strings.HasPrefix(str, parts[0]) {
- return false
- }
- // Check if string ends with last part
- if len(parts[len(parts)-1]) > 0 && !strings.HasSuffix(str, parts[len(parts)-1]) {
- return false
- }
- // Check middle parts
- searchStr := str
- if len(parts[0]) > 0 {
- searchStr = searchStr[len(parts[0]):]
- }
- if len(parts[len(parts)-1]) > 0 {
- searchStr = searchStr[:len(searchStr)-len(parts[len(parts)-1])]
- }
- for i := 1; i < len(parts)-1; i++ {
- if len(parts[i]) > 0 {
- index := strings.Index(searchStr, parts[i])
- if index == -1 {
- return false
- }
- searchStr = searchStr[index+len(parts[i]):]
- }
- }
- return true
- }
- // matchWildcardRegex uses WildcardMatcher for patterns with ? wildcards
- func matchWildcardRegex(pattern, str string) bool {
- matcher, err := GetCachedWildcardMatcher(pattern)
- if err != nil {
- glog.Errorf("Error getting WildcardMatcher for pattern %s: %v. Falling back to matchWildcardString.", pattern, err)
- // Fallback to matchWildcardString
- return matchWildcardString(pattern, str)
- }
- return matcher.Match(str)
- }
- // compileWildcardPattern converts a wildcard pattern to regex
- func compileWildcardPattern(pattern string) (*regexp.Regexp, error) {
- // Escape special regex characters except * and ?
- escaped := regexp.QuoteMeta(pattern)
- // Replace escaped wildcards with regex equivalents
- escaped = strings.ReplaceAll(escaped, `\*`, `.*`)
- escaped = strings.ReplaceAll(escaped, `\?`, `.`)
- // Anchor the pattern
- escaped = "^" + escaped + "$"
- return regexp.Compile(escaped)
- }
|