1 package net.anyflow.lannister.topic;
2
3 import java.io.UnsupportedEncodingException;
4 import java.util.List;
5
6 import com.google.common.base.Strings;
7
8 import net.anyflow.lannister.Settings;
9
10 public class TopicMatcher {
11
12 private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(TopicMatcher.class);
13
14 private static final String SEPARATOR = "/";
15 private static final String MULTI_LEVEL_WILDCARD = "#";
16 private static final String SINGLE_LEVEL_WILDCARD = "+";
17
18 private static final int MIN_LENGTH = 1;
19 private static final int MAX_LENGTH = 65535;
20 private static final char NULL = '\u0000';
21
22 private static final int INDEX_NOT_FOUND = -1;
23
24 private static final List<String> BANNED_TOPICFILTERS = Settings.INSTANCE.bannedTopicFilters();
25
26 public static final String MULTI_LEVEL_WILDCARD_PATTERN = SEPARATOR + MULTI_LEVEL_WILDCARD;
27 public static final String TOPIC_WILDCARDS = MULTI_LEVEL_WILDCARD + SINGLE_LEVEL_WILDCARD;
28
29 public static boolean isValid(String topicFilter, boolean allowWildcard) {
30 if (topicFilter == null) { return false; }
31 if (BANNED_TOPICFILTERS.contains(topicFilter)) { return false; }
32
33 int length = 0;
34 try {
35 length = topicFilter.getBytes("UTF-8").length;
36 }
37 catch (UnsupportedEncodingException e) {
38 logger.error(e.getMessage(), e);
39 return false;
40 }
41
42 if (length < MIN_LENGTH || length > MAX_LENGTH) {
43 logger.error("Invalid topic length [length={}", length);
44 return false;
45 }
46
47 if (allowWildcard) {
48 if (topicFilter.equals(MULTI_LEVEL_WILDCARD) || topicFilter.equals(SINGLE_LEVEL_WILDCARD)) { return true; }
49
50 if (countMatches(topicFilter, MULTI_LEVEL_WILDCARD) > 1 || (topicFilter.contains(MULTI_LEVEL_WILDCARD)
51 && !topicFilter.endsWith(MULTI_LEVEL_WILDCARD_PATTERN))) {
52 logger.error("Invalid usage of multi-level wildcard in topic string: {}", topicFilter);
53 return false;
54 }
55 }
56
57 return isValidSingleLevelWildcard(topicFilter);
58 }
59
60 private static int countMatches(String str, String sub) {
61 if (Strings.isNullOrEmpty(str) || Strings.isNullOrEmpty(sub)) { return 0; }
62
63 int count = 0;
64 int idx = 0;
65 while ((idx = str.indexOf(sub, idx)) != INDEX_NOT_FOUND) {
66 count++;
67 idx += sub.length();
68 }
69
70 return count;
71 }
72
73 private static boolean isValidSingleLevelWildcard(String topicString) {
74 char singleLevelWildcardChar = SINGLE_LEVEL_WILDCARD.charAt(0);
75 char topicLevelSeparatorChar = SEPARATOR.charAt(0);
76
77 char[] chars = topicString.toCharArray();
78 int length = chars.length;
79 char prev = NULL;
80 char next = NULL;
81
82 for (int i = 0; i < length; i++) {
83 prev = (i - 1 >= 0) ? chars[i - 1] : NULL;
84 next = (i + 1 < length) ? chars[i + 1] : NULL;
85
86 if ((chars[i] == singleLevelWildcardChar) && (prev != topicLevelSeparatorChar && prev != NULL
87 || next != topicLevelSeparatorChar && next != NULL)) {
88 logger.error("Invalid usage of single-level wildcard in topic string '{}'!", topicString);
89 return false;
90 }
91 }
92
93 return true;
94 }
95
96 public static boolean match(String topicFilter, String topicName) {
97 if (!TopicMatcher.isValid(topicFilter, true) || !TopicMatcher.isValid(topicName, false)) { return false; }
98
99 if ((topicFilter.startsWith(MULTI_LEVEL_WILDCARD) || topicFilter.startsWith(SINGLE_LEVEL_WILDCARD))
100 && topicName.startsWith("$")) { return false; }
101
102 String[] tfs = topicFilter.split("/");
103 String[] tns = topicName.split("/");
104
105 for (int i = 0; i < tns.length; ++i) {
106 if (i >= tfs.length) {
107 return false;
108 }
109 else if (tfs[i].equals(MULTI_LEVEL_WILDCARD)) {
110 return true;
111 }
112 else if (tfs[i].equals(SINGLE_LEVEL_WILDCARD)) {
113 continue;
114 }
115 else if (tfs[i].equals(tns[i])) {
116 continue;
117 }
118 else {
119 return false;
120 }
121 }
122
123 return true;
124 }
125 }