wildcard_matcher.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. package policy_engine
  2. import (
  3. "regexp"
  4. "strings"
  5. "sync"
  6. "github.com/seaweedfs/seaweedfs/weed/glog"
  7. )
  8. // WildcardMatcher provides unified wildcard matching functionality
  9. type WildcardMatcher struct {
  10. // Use regex for complex patterns with ? wildcards
  11. // Use string manipulation for simple * patterns (better performance)
  12. useRegex bool
  13. regex *regexp.Regexp
  14. pattern string
  15. }
  16. // WildcardMatcherCache provides caching for WildcardMatcher instances
  17. type WildcardMatcherCache struct {
  18. mu sync.RWMutex
  19. matchers map[string]*WildcardMatcher
  20. maxSize int
  21. accessOrder []string // For LRU eviction
  22. }
  23. // NewWildcardMatcherCache creates a new WildcardMatcherCache with a configurable maxSize
  24. func NewWildcardMatcherCache(maxSize int) *WildcardMatcherCache {
  25. if maxSize <= 0 {
  26. maxSize = 1000 // Default value
  27. }
  28. return &WildcardMatcherCache{
  29. matchers: make(map[string]*WildcardMatcher),
  30. maxSize: maxSize,
  31. }
  32. }
  33. // Global cache instance
  34. var wildcardMatcherCache = NewWildcardMatcherCache(1000) // Default maxSize
  35. // GetCachedWildcardMatcher gets or creates a cached WildcardMatcher for the given pattern
  36. func GetCachedWildcardMatcher(pattern string) (*WildcardMatcher, error) {
  37. // Fast path: check if already in cache
  38. wildcardMatcherCache.mu.RLock()
  39. if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists {
  40. wildcardMatcherCache.mu.RUnlock()
  41. wildcardMatcherCache.updateAccessOrder(pattern)
  42. return matcher, nil
  43. }
  44. wildcardMatcherCache.mu.RUnlock()
  45. // Slow path: create new matcher and cache it
  46. wildcardMatcherCache.mu.Lock()
  47. defer wildcardMatcherCache.mu.Unlock()
  48. // Double-check after acquiring write lock
  49. if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists {
  50. wildcardMatcherCache.updateAccessOrderLocked(pattern)
  51. return matcher, nil
  52. }
  53. // Create new matcher
  54. matcher, err := NewWildcardMatcher(pattern)
  55. if err != nil {
  56. return nil, err
  57. }
  58. // Evict old entries if cache is full
  59. if len(wildcardMatcherCache.matchers) >= wildcardMatcherCache.maxSize {
  60. wildcardMatcherCache.evictLeastRecentlyUsed()
  61. }
  62. // Cache it
  63. wildcardMatcherCache.matchers[pattern] = matcher
  64. wildcardMatcherCache.accessOrder = append(wildcardMatcherCache.accessOrder, pattern)
  65. return matcher, nil
  66. }
  67. // updateAccessOrder updates the access order for LRU eviction (with read lock)
  68. func (c *WildcardMatcherCache) updateAccessOrder(pattern string) {
  69. c.mu.Lock()
  70. defer c.mu.Unlock()
  71. c.updateAccessOrderLocked(pattern)
  72. }
  73. // updateAccessOrderLocked updates the access order for LRU eviction (without locking)
  74. func (c *WildcardMatcherCache) updateAccessOrderLocked(pattern string) {
  75. // Remove pattern from its current position
  76. for i, p := range c.accessOrder {
  77. if p == pattern {
  78. c.accessOrder = append(c.accessOrder[:i], c.accessOrder[i+1:]...)
  79. break
  80. }
  81. }
  82. // Add pattern to the end (most recently used)
  83. c.accessOrder = append(c.accessOrder, pattern)
  84. }
  85. // evictLeastRecentlyUsed removes the least recently used pattern from the cache
  86. func (c *WildcardMatcherCache) evictLeastRecentlyUsed() {
  87. if len(c.accessOrder) == 0 {
  88. return
  89. }
  90. // Remove the least recently used pattern (first in the list)
  91. lruPattern := c.accessOrder[0]
  92. c.accessOrder = c.accessOrder[1:]
  93. delete(c.matchers, lruPattern)
  94. }
  95. // ClearCache clears all cached patterns (useful for testing)
  96. func (c *WildcardMatcherCache) ClearCache() {
  97. c.mu.Lock()
  98. defer c.mu.Unlock()
  99. c.matchers = make(map[string]*WildcardMatcher)
  100. c.accessOrder = c.accessOrder[:0]
  101. }
  102. // GetCacheStats returns cache statistics
  103. func (c *WildcardMatcherCache) GetCacheStats() (size int, maxSize int) {
  104. c.mu.RLock()
  105. defer c.mu.RUnlock()
  106. return len(c.matchers), c.maxSize
  107. }
  108. // NewWildcardMatcher creates a new wildcard matcher for the given pattern
  109. func NewWildcardMatcher(pattern string) (*WildcardMatcher, error) {
  110. matcher := &WildcardMatcher{
  111. pattern: pattern,
  112. }
  113. // Determine if we need regex (contains ? wildcards)
  114. if strings.Contains(pattern, "?") {
  115. matcher.useRegex = true
  116. regex, err := compileWildcardPattern(pattern)
  117. if err != nil {
  118. return nil, err
  119. }
  120. matcher.regex = regex
  121. } else {
  122. matcher.useRegex = false
  123. }
  124. return matcher, nil
  125. }
  126. // Match checks if a string matches the wildcard pattern
  127. func (m *WildcardMatcher) Match(str string) bool {
  128. if m.useRegex {
  129. return m.regex.MatchString(str)
  130. }
  131. return matchWildcardString(m.pattern, str)
  132. }
  133. // MatchesWildcard provides a simple function interface for wildcard matching
  134. // This function consolidates the logic from the previous separate implementations
  135. func MatchesWildcard(pattern, str string) bool {
  136. // Handle simple cases first
  137. if pattern == "*" {
  138. return true
  139. }
  140. if pattern == str {
  141. return true
  142. }
  143. // Use regex for patterns with ? wildcards, string manipulation for * only
  144. if strings.Contains(pattern, "?") {
  145. return matchWildcardRegex(pattern, str)
  146. }
  147. return matchWildcardString(pattern, str)
  148. }
  149. // CompileWildcardPattern converts a wildcard pattern to a compiled regex
  150. // This replaces the previous compilePattern function
  151. func CompileWildcardPattern(pattern string) (*regexp.Regexp, error) {
  152. return compileWildcardPattern(pattern)
  153. }
  154. // matchWildcardString uses string manipulation for * wildcards only (more efficient)
  155. func matchWildcardString(pattern, str string) bool {
  156. // Handle simple cases
  157. if pattern == "*" {
  158. return true
  159. }
  160. if pattern == str {
  161. return true
  162. }
  163. // Split pattern by wildcards
  164. parts := strings.Split(pattern, "*")
  165. if len(parts) == 1 {
  166. // No wildcards, exact match
  167. return pattern == str
  168. }
  169. // Check if string starts with first part
  170. if len(parts[0]) > 0 && !strings.HasPrefix(str, parts[0]) {
  171. return false
  172. }
  173. // Check if string ends with last part
  174. if len(parts[len(parts)-1]) > 0 && !strings.HasSuffix(str, parts[len(parts)-1]) {
  175. return false
  176. }
  177. // Check middle parts
  178. searchStr := str
  179. if len(parts[0]) > 0 {
  180. searchStr = searchStr[len(parts[0]):]
  181. }
  182. if len(parts[len(parts)-1]) > 0 {
  183. searchStr = searchStr[:len(searchStr)-len(parts[len(parts)-1])]
  184. }
  185. for i := 1; i < len(parts)-1; i++ {
  186. if len(parts[i]) > 0 {
  187. index := strings.Index(searchStr, parts[i])
  188. if index == -1 {
  189. return false
  190. }
  191. searchStr = searchStr[index+len(parts[i]):]
  192. }
  193. }
  194. return true
  195. }
  196. // matchWildcardRegex uses WildcardMatcher for patterns with ? wildcards
  197. func matchWildcardRegex(pattern, str string) bool {
  198. matcher, err := GetCachedWildcardMatcher(pattern)
  199. if err != nil {
  200. glog.Errorf("Error getting WildcardMatcher for pattern %s: %v. Falling back to matchWildcardString.", pattern, err)
  201. // Fallback to matchWildcardString
  202. return matchWildcardString(pattern, str)
  203. }
  204. return matcher.Match(str)
  205. }
  206. // compileWildcardPattern converts a wildcard pattern to regex
  207. func compileWildcardPattern(pattern string) (*regexp.Regexp, error) {
  208. // Escape special regex characters except * and ?
  209. escaped := regexp.QuoteMeta(pattern)
  210. // Replace escaped wildcards with regex equivalents
  211. escaped = strings.ReplaceAll(escaped, `\*`, `.*`)
  212. escaped = strings.ReplaceAll(escaped, `\?`, `.`)
  213. // Anchor the pattern
  214. escaped = "^" + escaped + "$"
  215. return regexp.Compile(escaped)
  216. }