1 package org.apache.helix.alerts;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.StringTokenizer;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30
31 import org.apache.helix.HelixException;
32 import org.apache.log4j.Logger;
33
34
35 public class ExpressionParser
36 {
37 private static Logger logger = Logger.getLogger(ExpressionParser.class);
38
39 final static String opDelim = "|";
40 final static String opDelimForSplit = "\\|";
41 final static String argDelim = ",";
42 final public static String statFieldDelim = ".";
43 final static String wildcardChar = "*";
44
45
46
47
48 static Map<String, Operator> operatorMap = new HashMap<String, Operator>();
49 static Map<String, Aggregator> aggregatorMap = new HashMap<String, Aggregator>();
50
51 static
52 {
53
54 addOperatorEntry("EXPAND", new ExpandOperator());
55 addOperatorEntry("DIVIDE", new DivideOperator());
56 addOperatorEntry("SUM", new SumOperator());
57 addOperatorEntry("SUMEACH", new SumEachOperator());
58
59 addAggregatorEntry("ACCUMULATE", new AccumulateAggregator());
60 addAggregatorEntry("DECAY", new DecayAggregator());
61 addAggregatorEntry("WINDOW", new WindowAggregator());
62
63
64
65
66
67
68 }
69
70
71
72 private static void addOperatorEntry(String label, Operator op)
73 {
74 if (!operatorMap.containsKey(label))
75 {
76 operatorMap.put(label, op);
77 }
78 logger.info("Adding operator: " + op);
79 }
80
81 private static void addAggregatorEntry(String label, Aggregator agg)
82 {
83 if (!aggregatorMap.containsKey(label.toUpperCase()))
84 {
85 aggregatorMap.put(label.toUpperCase(), agg);
86 }
87 logger.info("Adding aggregator: " + agg);
88 }
89
90
91
92
93
94
95
96 public static boolean isExpressionNested(String expression)
97 {
98 return expression.contains("(");
99 }
100
101
102
103
104
105
106
107
108 public static String getInnerExpression(String expression)
109 {
110 return expression.substring(expression.indexOf("(") + 1,
111 expression.lastIndexOf(")"));
112 }
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 public static void validateAggregatorFormat(String expression)
138 throws HelixException
139 {
140 logger.debug("validating aggregator for expression: " + expression);
141
142 Pattern pattern = Pattern.compile("\\(.*?\\)");
143 Matcher matcher = pattern.matcher(expression);
144 String aggComponent = null;
145 String statComponent = null;
146 int lastMatchEnd = -1;
147 if (matcher.find())
148 {
149 aggComponent = matcher.group();
150 aggComponent = aggComponent.substring(1, aggComponent.length() - 1);
151 if (aggComponent.contains(")") || aggComponent.contains("("))
152 {
153 throw new HelixException(expression
154 + " has invalid aggregate component");
155 }
156 }
157 else
158 {
159 throw new HelixException(expression + " has invalid aggregate component");
160 }
161 if (matcher.find())
162 {
163 statComponent = matcher.group();
164 statComponent = statComponent.substring(1, statComponent.length() - 1);
165
166 if (statComponent.contains(")") || statComponent.contains("(")
167 || statComponent.length() == 0)
168 {
169 throw new HelixException(expression + " has invalid stat component");
170 }
171 lastMatchEnd = matcher.end();
172 }
173 else
174 {
175 throw new HelixException(expression + " has invalid stat component");
176 }
177 if (matcher.find())
178 {
179 throw new HelixException(expression
180 + " has too many parenthesis components");
181 }
182
183 if (expression.length() >= lastMatchEnd + 1)
184 {
185 if (expression.substring(lastMatchEnd).contains("(")
186 || expression.substring(lastMatchEnd).contains(")"))
187 {
188 throw new HelixException(expression + " has extra parenthesis");
189 }
190 }
191
192
193
194
195 StringTokenizer fieldTok = new StringTokenizer(statComponent,
196 statFieldDelim);
197 while (fieldTok.hasMoreTokens())
198 {
199 String currTok = fieldTok.nextToken();
200 if (currTok.contains(wildcardChar))
201 {
202 if (currTok.indexOf(wildcardChar) != currTok.length() - 1
203 || currTok.lastIndexOf(wildcardChar) != currTok.length() - 1)
204 {
205 throw new HelixException(currTok
206 + " is illegal stat name. Single wildcard must appear at end.");
207 }
208 }
209 }
210 }
211
212 public static boolean statContainsWildcards(String stat)
213 {
214 return stat.contains(wildcardChar);
215 }
216
217
218
219
220
221
222
223
224
225
226
227 public static boolean isExactMatch(String currentStat, String incomingStat,
228 boolean extractStatFromAgg)
229 {
230 String currentStatName = currentStat;
231 if (extractStatFromAgg)
232 {
233 currentStatName = getSingleAggregatorStat(currentStat);
234 }
235 return (incomingStat.equals(currentStatName));
236 }
237
238
239
240
241
242
243
244
245
246
247 public static boolean isWildcardMatch(String currentStat,
248 String incomingStat, boolean statCompareOnly, ArrayList<String> bindings)
249 {
250 if (!statCompareOnly)
251 {
252 String currentStatAggType = (currentStat.split("\\)"))[0];
253 String incomingStatAggType = (incomingStat.split("\\)"))[0];
254 if (!currentStatAggType.equals(incomingStatAggType))
255 {
256 return false;
257 }
258 }
259
260 String currentStatName = getSingleAggregatorStat(currentStat);
261 String incomingStatName = getSingleAggregatorStat(incomingStat);
262
263 if (!currentStatName.contains(wildcardChar))
264 {
265 return false;
266 }
267
268 String currentStatNamePattern = currentStatName.replace(".", "\\.");
269 currentStatNamePattern = currentStatNamePattern.replace("*", ".*");
270 boolean result = Pattern.matches(currentStatNamePattern, incomingStatName);
271 if(result && bindings != null)
272 {
273 bindings.add(incomingStatName);
274 }
275 return result;
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338 }
339
340
341
342
343
344 public static boolean isIncomingStatExactMatch(String currentStat,
345 String incomingStat)
346 {
347 return isExactMatch(currentStat, incomingStat, true);
348 }
349
350
351
352
353
354
355 public static boolean isIncomingStatWildcardMatch(String currentStat,
356 String incomingStat)
357 {
358 return isWildcardMatch(currentStat, incomingStat, true, null);
359 }
360
361
362
363
364 public static boolean isAlertStatExactMatch(String alertStat,
365 String currentStat)
366 {
367 return isExactMatch(alertStat, currentStat, false);
368 }
369
370
371
372
373
374 public static boolean isAlertStatWildcardMatch(String alertStat,
375 String currentStat, ArrayList<String> wildcardBindings)
376 {
377 return isWildcardMatch(alertStat, currentStat, false, wildcardBindings);
378 }
379
380 public static Aggregator getAggregator(String aggStr) throws HelixException
381 {
382 aggStr = aggStr.toUpperCase();
383 Aggregator agg = aggregatorMap.get(aggStr);
384 if (agg == null)
385 {
386 throw new HelixException("Unknown aggregator type " + aggStr);
387 }
388 return agg;
389 }
390
391 public static String getAggregatorStr(String expression)
392 throws HelixException
393 {
394 if (!expression.contains("("))
395 {
396 throw new HelixException(expression
397 + " does not contain a valid aggregator. No parentheses found");
398 }
399 String aggName = expression.substring(0, expression.indexOf("("));
400 if (!aggregatorMap.containsKey(aggName.toUpperCase()))
401 {
402 throw new HelixException("aggregator <" + aggName + "> is unknown type");
403 }
404 return aggName;
405 }
406
407 public static String[] getAggregatorArgs(String expression)
408 throws HelixException
409 {
410 String aggregator = getAggregatorStr(expression);
411 String argsStr = getAggregatorArgsStr(expression);
412 String[] args = argsStr.split(argDelim);
413 logger.debug("args size: " + args.length);
414 int numArgs = (argsStr.length() == 0) ? 0 : args.length;
415
416
417
418 int requiredNumArgs = aggregatorMap.get(aggregator.toUpperCase())
419 .getRequiredNumArgs();
420 if (numArgs != requiredNumArgs)
421 {
422 throw new HelixException(expression + " contains " + args.length
423 + " arguments, but requires " + requiredNumArgs);
424 }
425 return args;
426 }
427
428
429
430
431
432
433
434 public static String getAggregatorArgsStr(String expression)
435 {
436 return expression.substring(expression.indexOf("(") + 1,
437 expression.indexOf(")"));
438 }
439
440 public static String[] getAggregatorStats(String expression)
441 throws HelixException
442 {
443 String justStats = expression;
444 if (expression.contains("(") && expression.contains(")"))
445 {
446 justStats = (expression.substring(expression.lastIndexOf("(") + 1,
447 expression.lastIndexOf(")")));
448 }
449 String[] statList = justStats.split(argDelim);
450 if (statList.length < 1)
451 {
452 throw new HelixException(expression
453 + " does not contain any aggregator stats");
454 }
455 return statList;
456 }
457
458 public static String getSingleAggregatorStat(String expression)
459 throws HelixException
460 {
461 String[] stats = getAggregatorStats(expression);
462 if (stats.length > 1)
463 {
464 throw new HelixException(expression + " contains more than 1 stat");
465 }
466 return stats[0];
467 }
468
469 public static String getWildcardStatSubstitution(String wildcardStat,
470 String fixedStat)
471 {
472 int lastOpenParenLoc = wildcardStat.lastIndexOf("(");
473 int lastCloseParenLoc = wildcardStat.lastIndexOf(")");
474 StringBuilder builder = new StringBuilder();
475 builder.append(wildcardStat.substring(0, lastOpenParenLoc + 1));
476 builder.append(fixedStat);
477 builder.append(")");
478 logger.debug("wildcardStat: " + wildcardStat);
479 logger.debug("fixedStat: " + fixedStat);
480 logger.debug("subbedStat: " + builder.toString());
481 return builder.toString();
482 }
483
484
485
486
487 public static String[] getBaseStats(String expression) throws HelixException
488 {
489 expression = expression.replaceAll("\\s+", "");
490 validateAggregatorFormat(expression);
491
492 String aggName = getAggregatorStr(expression);
493 String[] aggArgs = getAggregatorArgs(expression);
494 String[] aggStats = getAggregatorStats(expression);
495
496
497 String aggArgList = getAggregatorArgsStr(expression);
498
499 String[] baseStats = new String[aggStats.length];
500 for (int i = 0; i < aggStats.length; i++)
501 {
502 StringBuilder stat = new StringBuilder();
503 stat.append(aggName);
504 stat.append("(");
505 stat.append(aggArgList);
506 stat.append(")");
507 stat.append("(");
508 stat.append(aggStats[i]);
509 stat.append(")");
510 baseStats[i] = stat.toString();
511 }
512 return baseStats;
513 }
514
515 public static String[] getOperators(String expression) throws HelixException
516 {
517 String[] ops = null;
518 int numAggStats = (getAggregatorStats(expression)).length;
519 int opDelimLoc = expression.indexOf(opDelim);
520 if (opDelimLoc < 0)
521 {
522 return null;
523 }
524 logger.debug("ops str: " + expression.substring(opDelimLoc + 1));
525 ops = expression.substring(opDelimLoc + 1).split(opDelimForSplit);
526
527
528
529
530
531 int currNumTuples = numAggStats;
532 for (String op : ops)
533 {
534 logger.debug("op: " + op);
535 if (!operatorMap.containsKey(op.toUpperCase()))
536 {
537 throw new HelixException("<" + op + "> is not a valid operator type");
538 }
539 Operator currOpType = operatorMap.get(op.toUpperCase());
540 if (currNumTuples < currOpType.minInputTupleLists
541 || currNumTuples > currOpType.maxInputTupleLists)
542 {
543 throw new HelixException("<" + op + "> cannot process " + currNumTuples
544 + " input tuples");
545 }
546
547 if (!currOpType.inputOutputTupleListsCountsEqual)
548 {
549 currNumTuples = currOpType.numOutputTupleLists;
550 }
551 }
552 if (currNumTuples != 1)
553 {
554 throw new HelixException(expression
555 + " does not terminate in a single tuple set");
556 }
557 return ops;
558 }
559
560 public static void validateOperators(String expression) throws HelixException
561 {
562 getOperators(expression);
563 }
564
565 public static Operator getOperator(String opName) throws HelixException
566 {
567 if (!operatorMap.containsKey(opName))
568 {
569 throw new HelixException(opName + " is unknown op type");
570 }
571 return operatorMap.get(opName);
572 }
573
574 public static void validateExpression(String expression)
575 throws HelixException
576 {
577
578 validateAggregatorFormat(expression);
579
580
581 validateOperators(expression);
582 }
583 }