1+ package io .kurrent .dbclient .v2 ;
2+
3+ import io .kurrent .dbclient .SubscriptionFilter ;
4+ import io .kurrent .dbclient .SubscriptionFilterBuilder ;
5+
6+ import javax .validation .constraints .NotNull ;
7+ import java .util .Objects ;
8+ import java .util .regex .Pattern ;
9+ import java .util .regex .Matcher ;
10+
11+ /**
12+ * Represents a filter for consuming events.
13+ */
14+ public class ConsumeFilter {
15+ /**
16+ * Represents an empty filter.
17+ */
18+ public static final ConsumeFilter NONE = new ConsumeFilter ();
19+
20+ private final ConsumeFilterScope scope ;
21+ private final ConsumeFilterType type ;
22+ private final String expression ;
23+ private final Pattern regex ;
24+
25+ /**
26+ * Creates a new empty consume filter.
27+ */
28+ public ConsumeFilter () {
29+ this .scope = ConsumeFilterScope .UNSPECIFIED ;
30+ this .type = ConsumeFilterType .UNSPECIFIED ;
31+ this .expression = "" ;
32+ this .regex = Pattern .compile ("" );
33+ }
34+
35+ /**
36+ * Creates a new consume filter with the specified parameters.
37+ *
38+ * @param scope The scope of the filter.
39+ * @param type The type of the filter.
40+ * @param expression The filter expression.
41+ * @param regex The compiled regex pattern.
42+ */
43+ private ConsumeFilter (ConsumeFilterScope scope , ConsumeFilterType type , String expression , Pattern regex ) {
44+ this .scope = scope ;
45+ this .type = type ;
46+ this .expression = expression ;
47+ this .regex = regex ;
48+ }
49+
50+ /**
51+ * Gets the scope of the filter.
52+ *
53+ * @return The filter scope.
54+ */
55+ public ConsumeFilterScope getScope () {
56+ return scope ;
57+ }
58+
59+ /**
60+ * Gets the type of the filter.
61+ *
62+ * @return The filter type.
63+ */
64+ public ConsumeFilterType getType () {
65+ return type ;
66+ }
67+
68+ /**
69+ * Gets the filter expression.
70+ *
71+ * @return The filter expression.
72+ */
73+ public String getExpression () {
74+ return expression ;
75+ }
76+
77+ /**
78+ * Checks if this is a literal filter.
79+ *
80+ * @return True if this is a literal filter, false otherwise.
81+ */
82+ public boolean isLiteralFilter () {
83+ return type == ConsumeFilterType .LITERAL ;
84+ }
85+
86+ /**
87+ * Checks if this is a regex filter.
88+ *
89+ * @return True if this is a regex filter, false otherwise.
90+ */
91+ public boolean isRegexFilter () {
92+ return type == ConsumeFilterType .REGEX ;
93+ }
94+
95+ /**
96+ * Checks if this is a stream filter.
97+ *
98+ * @return True if this is a stream filter, false otherwise.
99+ */
100+ public boolean isStreamFilter () {
101+ return scope == ConsumeFilterScope .STREAM ;
102+ }
103+
104+ /**
105+ * Checks if this is a record filter.
106+ *
107+ * @return True if this is a record filter, false otherwise.
108+ */
109+ public boolean isRecordFilter () {
110+ return scope == ConsumeFilterScope .RECORD ;
111+ }
112+
113+ /**
114+ * Checks if this is a stream name filter.
115+ *
116+ * @return True if this is a stream name filter, false otherwise.
117+ */
118+ public boolean isStreamNameFilter () {
119+ return type == ConsumeFilterType .LITERAL && scope == ConsumeFilterScope .STREAM ;
120+ }
121+
122+ /**
123+ * Checks if this is an empty filter.
124+ *
125+ * @return True if this is an empty filter, false otherwise.
126+ */
127+ public boolean isEmptyFilter () {
128+ return type == ConsumeFilterType .UNSPECIFIED && scope == ConsumeFilterScope .UNSPECIFIED ;
129+ }
130+
131+ /**
132+ * Checks if the input matches this filter.
133+ *
134+ * @param input The input to check.
135+ * @return True if the input matches this filter, false otherwise.
136+ */
137+ public boolean isMatch (CharSequence input ) {
138+ return regex .matcher (input ).matches ();
139+ }
140+
141+ /**
142+ * Creates a stream filter from a stream name.
143+ *
144+ * @param stream The stream name.
145+ * @return A new consume filter for the specified stream.
146+ * @throws IllegalArgumentException If the stream name is invalid.
147+ */
148+ public static ConsumeFilter fromStream (String stream ) {
149+ if (stream == null || stream .trim ().isEmpty ())
150+ throw new IllegalArgumentException ("Stream name cannot be null or whitespace." );
151+
152+ if (stream .startsWith ("~" ))
153+ throw new IllegalArgumentException ("Stream name cannot start with '~'." );
154+
155+ if (stream .length () < 2 )
156+ throw new IllegalArgumentException ("Stream name must be at least 2 characters long." );
157+
158+ return new ConsumeFilter (
159+ ConsumeFilterScope .STREAM ,
160+ ConsumeFilterType .LITERAL ,
161+ stream ,
162+ Pattern .compile (Pattern .quote (stream ))
163+ );
164+ }
165+
166+ /**
167+ * Creates a filter from prefixes.
168+ *
169+ * @param scope The scope of the filter.
170+ * @param prefixes The prefixes to filter by.
171+ * @return A new consume filter for the specified prefixes.
172+ * @throws IllegalArgumentException If the prefixes are invalid.
173+ */
174+ public static ConsumeFilter fromPrefixes (ConsumeFilterScope scope , String ... prefixes ) {
175+ if (prefixes .length == 0 )
176+ throw new IllegalArgumentException ("Prefixes cannot be empty." );
177+
178+ StringBuilder patternBuilder = new StringBuilder ("^(" );
179+ boolean first = true ;
180+
181+ for (String prefix : prefixes ) {
182+ if (prefix == null || prefix .trim ().isEmpty ())
183+ throw new IllegalArgumentException ("Prefix cannot be empty." );
184+
185+ if (!first )
186+ patternBuilder .append ("|" );
187+
188+ patternBuilder .append (Pattern .quote (prefix ));
189+ first = false ;
190+ }
191+ patternBuilder .append (")" );
192+
193+ String pattern = patternBuilder .toString ();
194+ return new ConsumeFilter (
195+ scope ,
196+ ConsumeFilterType .REGEX ,
197+ pattern ,
198+ Pattern .compile (pattern )
199+ );
200+ }
201+
202+ /**
203+ * Creates a filter from a comma-separated list of prefixes.
204+ *
205+ * @param scope The scope of the filter.
206+ * @param expression The comma-separated list of prefixes.
207+ * @return A new consume filter for the specified prefixes.
208+ * @throws IllegalArgumentException If the expression is invalid.
209+ */
210+ public static ConsumeFilter fromPrefixes (ConsumeFilterScope scope , String expression ) {
211+ if (expression == null || expression .trim ().isEmpty ())
212+ throw new IllegalArgumentException ("Prefix expression cannot be empty." );
213+
214+ return fromPrefixes (scope , expression .split ("," ));
215+ }
216+
217+ /**
218+ * Creates a filter from a regex pattern.
219+ *
220+ * @param scope The scope of the filter.
221+ * @param pattern The regex pattern.
222+ * @return A new consume filter for the specified regex pattern.
223+ * @throws IllegalArgumentException If the pattern is invalid.
224+ */
225+ public static ConsumeFilter fromRegex (ConsumeFilterScope scope , String pattern ) {
226+ String expression = pattern .startsWith ("~" ) ? pattern .substring (1 ) : pattern ;
227+
228+ try {
229+ return new ConsumeFilter (
230+ scope ,
231+ ConsumeFilterType .REGEX ,
232+ expression ,
233+ Pattern .compile (expression )
234+ );
235+ } catch (Exception ex ) {
236+ throw new IllegalArgumentException ("Invalid regex pattern: " + pattern , ex );
237+ }
238+ }
239+
240+ /**
241+ * Creates a filter from an expression.
242+ *
243+ * @param scope The scope of the filter.
244+ * @param expression The filter expression.
245+ * @return A new consume filter for the specified expression.
246+ * @throws IllegalArgumentException If the expression is invalid.
247+ */
248+ public static ConsumeFilter create (ConsumeFilterScope scope , String expression ) {
249+ if (expression == null || expression .trim ().isEmpty ())
250+ throw new IllegalArgumentException ("Expression cannot be null or empty." );
251+
252+ if (expression .startsWith ("~" )) {
253+ return fromRegex (scope , expression );
254+ } else {
255+ return new ConsumeFilter (
256+ scope ,
257+ ConsumeFilterType .LITERAL ,
258+ expression ,
259+ Pattern .compile (Pattern .quote (expression ))
260+ );
261+ }
262+ }
263+
264+ /**
265+ * Converts a ConsumeFilter to a SubscriptionFilter.
266+ *
267+ * @param checkpointInterval The checkpoint interval to use.
268+ * @return The converted subscription filter, or null if the filter is empty.
269+ * @throws IllegalArgumentException If the filter is invalid.
270+ */
271+ public SubscriptionFilter toSubscriptionFilter (int checkpointInterval ) {
272+ if (isEmptyFilter ())
273+ return null ;
274+
275+ SubscriptionFilterBuilder builder = SubscriptionFilter .newBuilder ();
276+
277+ // Set the checkpoint interval
278+ builder .withMaxWindow (checkpointInterval );
279+
280+ // Configure the filter based on its scope and type
281+ if (isStreamFilter ()) {
282+ if (isRegexFilter ()) {
283+ builder .withStreamNameRegularExpression (getExpression ());
284+ } else if (isLiteralFilter ()) {
285+ builder .addStreamNamePrefix (getExpression ());
286+ }
287+ } else if (isRecordFilter ()) {
288+ if (isRegexFilter ()) {
289+ builder .withEventTypeRegularExpression (getExpression ());
290+ } else if (isLiteralFilter ()) {
291+ builder .addEventTypePrefix (getExpression ());
292+ }
293+ } else {
294+ throw new IllegalArgumentException ("Invalid consume filter." );
295+ }
296+
297+ return builder .build ();
298+ }
299+
300+ @ Override
301+ public String toString () {
302+ return "[" + scope + "|" + type + "] " + expression ;
303+ }
304+
305+ @ Override
306+ public boolean equals (Object o ) {
307+ if (this == o ) return true ;
308+ if (o == null || getClass () != o .getClass ()) return false ;
309+ ConsumeFilter that = (ConsumeFilter ) o ;
310+ return scope == that .scope &&
311+ type == that .type &&
312+ Objects .equals (expression , that .expression );
313+ }
314+
315+ @ Override
316+ public int hashCode () {
317+ return Objects .hash (scope , type , expression );
318+ }
319+ }
0 commit comments