package org.n3r.eql.parser; import com.google.common.base.Splitter; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import lombok.extern.slf4j.Slf4j; import lombok.val; import org.n3r.eql.base.DynamicLanguageDriver; import org.n3r.eql.base.EqlResourceLoader; import org.n3r.eql.impl.DefaultDynamicLanguageDriver; import org.n3r.eql.settings.EqlFileGlobalSettings; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Parse a whole eql file. */ @Slf4j public class EqlParser { static Pattern blockPattern = Pattern.compile("\\[\\s*([\\w\\.\\-\\d]+)\\b(.*)\\]"); private Map<String, EqlBlock> blocks = Maps.newHashMap(); private String sqlClassPath; private List<String> sqlLines = null; private DynamicLanguageDriver dynamicLanguageDriver; private EqlBlock block = null; private EqlResourceLoader eqlResourceLoader; private boolean sqlParseDelay; public EqlParser(EqlResourceLoader eqlResourceLoader, String sqlClassPath) { this.eqlResourceLoader = eqlResourceLoader; this.sqlClassPath = sqlClassPath; if (eqlResourceLoader != null) dynamicLanguageDriver = eqlResourceLoader.getDynamicLanguageDriver(); if (dynamicLanguageDriver == null) dynamicLanguageDriver = new DefaultDynamicLanguageDriver(); } // delay sql parse public Map<String, EqlBlock> delayParse(String eqlStr) { this.sqlParseDelay = true; return parse(eqlStr); } public Map<String, EqlBlock> parse(String eqlStr) { Iterable<String> lines = Splitter.on('\n').trimResults().split(eqlStr); int lineNo = 0; for (String line : lines) { ++lineNo; line = line.trim(); if (line.length() == 0) continue; if (line.startsWith("--")) { String cleanLine = ParserUtils.substr(line, "--".length()); if (importOtherSqlFile(cleanLine)) continue; if (includeOtherSqlId(cleanLine)) continue; if (globalSettings(cleanLine)) continue; Matcher matcher = blockPattern.matcher(cleanLine); if (matcher.matches()) { // new sql block found parsePreviousBlock(); String sqlId = matcher.group(1); String options = matcher.group(2); block = new EqlBlock(sqlClassPath, sqlId, options, lineNo); addBlock(block); continue; } } if (block == null) continue; // without any block, just ignore sqlLines.add(line); } parsePreviousBlock(); return blocks; } static Pattern globalSettingsPattern = Pattern.compile("global\\s+settings\\s+(.+)"); private boolean globalSettings(String cleanLine) { Matcher matcher = globalSettingsPattern.matcher(cleanLine); if (!matcher.matches()) return false; String globalSettings = matcher.group(1).trim(); EqlFileGlobalSettings.process(sqlClassPath, globalSettings); return true; } static Pattern includePattern = Pattern.compile("(include|ref)\\s+([\\w\\.\\-\\d]+)"); private boolean includeOtherSqlId(String cleanLine) { Matcher matcher = includePattern.matcher(cleanLine); if (!matcher.matches()) return false; if (block == null) return true; String includeEqlId = matcher.group(2); EqlBlock eqlBlock = blocks.get(includeEqlId); if (eqlBlock == null) { log.error("include eql id {} not found in {}", includeEqlId, sqlClassPath); throw new RuntimeException(cleanLine + " not found"); } sqlLines.addAll(eqlBlock.getSqlLines()); String ref = matcher.group(1); if (!"ref".equals(ref)) sqlLines.add(";"); return true; } static Pattern importPattern = Pattern.compile("import\\s+([/.\\w]+)(\\s+.*)?"); private boolean importOtherSqlFile(String cleanLine) { Matcher matcher = importPattern.matcher(cleanLine); if (!matcher.matches()) return false; parsePreviousBlock(); String classPath = matcher.group(1).trim(); String patterns = ParserUtils.trim(matcher.group(2)); if (classPath.equals(sqlClassPath)) return true; val importRes = eqlResourceLoader.load(classPath); if (ParserUtils.isBlank(patterns)) { importSqlBlocks(cleanLine, importRes); return true; } Map<String, EqlBlock> temp = Maps.newHashMap(); Splitter splitter = Splitter.onPattern("\\s+").omitEmptyStrings().trimResults(); for (String pattern : splitter.split(patterns)) { for (EqlBlock eqlBlock : importRes.values()) { if (wildCardMatch(eqlBlock.getSqlId(), pattern)) { temp.put(eqlBlock.getSqlId(), eqlBlock); } } } importSqlBlocks(cleanLine, temp); return true; } private void importSqlBlocks(String cleanLine, Map<String, EqlBlock> temp) { for (EqlBlock eqlBlock : temp.values()) { if (blocks.containsKey(eqlBlock.getSqlId())) { log.error("{} duplicated when {} in {}", eqlBlock.getSqlId(), cleanLine, sqlClassPath); throw new RuntimeException(eqlBlock.getSqlId() + " duplicated when " + cleanLine + " in " + sqlClassPath); } blocks.put(eqlBlock.getSqlId(), eqlBlock); } } private void addBlock(EqlBlock eqlBlock) { if (blocks.containsKey(eqlBlock.getSqlId()) && !eqlBlock.isOverride()) { log.error("{} duplicated in {}", eqlBlock.getSqlId(), sqlClassPath); throw new RuntimeException(eqlBlock.getSqlId() + " duplicated in " + sqlClassPath); } blocks.put(eqlBlock.getSqlId(), eqlBlock); sqlLines = Lists.<String>newArrayList(); } private void parsePreviousBlock() { if (block != null && sqlLines != null && sqlLines.size() > 0) { new EqlBlockParser(dynamicLanguageDriver, sqlParseDelay).parse(block, sqlLines); block = null; sqlLines = null; } } public static boolean wildCardMatch(String text, String pattern) { // Create the cards by splitting using a RegEx. If more speed // is desired, a simpler character based splitting can be done. String[] cards = pattern.split("\\*"); for (String card : cards) { int idx = text.indexOf(card); if (idx == -1) return false; text = text.substring(idx + card.length()); } return true; } }