/* * RHQ Management Platform * Copyright (C) 2014 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.server.search.execution; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.antlr.runtime.ANTLRStringStream; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.Token; import org.antlr.runtime.tree.CommonTree; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.search.SearchSubsystem; import org.rhq.enterprise.server.search.RHQLLexer; import org.rhq.enterprise.server.search.RHQLParser; import org.rhq.enterprise.server.search.antlr.RHQLNodeAdaptor; import org.rhq.enterprise.server.search.assist.SearchAssistant; import org.rhq.enterprise.server.search.assist.SearchAssistantFactory; import org.rhq.enterprise.server.search.translation.SearchTranslator; import org.rhq.enterprise.server.search.translation.SearchTranslatorFactory; import org.rhq.enterprise.server.search.translation.antlr.RHQLAdvancedTerm; import org.rhq.enterprise.server.search.translation.antlr.RHQLComparisonOperator; import org.rhq.enterprise.server.search.translation.antlr.RHQLSimpleTerm; import org.rhq.enterprise.server.search.translation.antlr.RHQLTerm; import org.rhq.enterprise.server.search.translation.antlr.RHQLTreeOperator; import org.rhq.enterprise.server.search.translation.jpql.SearchFragment; /** * @author Joseph Marques */ public class SearchTranslationManager { private static final Log LOG = LogFactory.getLog(SearchTranslationManager.class); private SearchSubsystem context; private String expression; private SearchTranslator translator; private SearchAssistant assistant; private RHQLLexer lexer; private RHQLParser parser; private RHQLNodeAdaptor adaptor; private String entity; private String alias; private Subject subject; public SearchTranslationManager(String alias, Subject subject, SearchSubsystem context) { this.subject = subject; this.context = context; this.entity = this.context.getEntityClass().getSimpleName(); this.alias = alias; } public void setExpression(String expression) { if (expression == null) { expression = ""; } else { expression = expression.trim(); } this.expression = expression; this.translator = SearchTranslatorFactory.getTranslator(subject, this.context); this.assistant = SearchAssistantFactory.getAssistant(subject, this.context); ANTLRStringStream input = new ANTLRStringStream(this.expression); // Create an input character stream from standard in this.lexer = new RHQLLexer(input); // Create an echoLexer that feeds from that stream CommonTokenStream tokens = new CommonTokenStream(this.lexer); // Create a stream of tokens fed by the lexer this.parser = new RHQLParser(tokens); // Create a parser that feeds off the token stream this.adaptor = new RHQLNodeAdaptor(); parser.setTreeAdaptor(adaptor); } public String getJPQLSelectStatement() throws Exception { String jpql = "SELECT " + alias + " FROM " + entity + " " + alias + " WHERE " + getJPQLWhereFragment(); if (LOG.isDebugEnabled()) { LOG.debug("JPQL was:"); PrintUtils.printJPQL(jpql.split(" ")); } return jpql; } public String getJPQLWhereFragment() throws Exception { RHQLParser.searchExpression_return searchAST = parser.searchExpression(); CommonTree searchExpressionTree = searchAST.getTree(); if (LOG.isDebugEnabled()) { LOG.debug("Search was: " + expression); LOG.debug("Errors found: " + adaptor.getErrorMessages()); LOG.debug("Tree was:"); PrintUtils.print(searchExpressionTree, ""); } String fragment = generateJPQL(searchExpressionTree); return fragment; } private String generateJPQL(CommonTree tree) { StringBuilder builder = new StringBuilder(); builder.append(" ( "); Token token = tree.getToken(); if (token == null) { return null; } RHQLTreeOperator operator = getTreeOperatorFromTokenType(token.getType()); if (operator != null) { for (int childIndex = 0; childIndex < tree.getChildCount(); childIndex++) { CommonTree child = (CommonTree) tree.getChild(childIndex); if (childIndex != 0) { builder.append(" " + operator.name() + " "); } builder.append(generateJPQL(child)); } } else { List<RHQLTerm> terms = getFromAST(tree); boolean first = true; for (RHQLTerm nextTerm : terms) { if (first == false) { builder.append(" AND "); // implicit AND for IN-clause values } else { first = false; } RHQLAdvancedTerm advancedTerm = null; if (nextTerm instanceof RHQLSimpleTerm) { RHQLSimpleTerm simpleTerm = (RHQLSimpleTerm) nextTerm; advancedTerm = new RHQLAdvancedTerm(null, assistant.getPrimarySimpleContext(), null, RHQLComparisonOperator.EQUALS, simpleTerm.getValue()); } else { advancedTerm = (RHQLAdvancedTerm) nextTerm; } SearchFragment searchFragment = translator.getSearchFragment(alias, advancedTerm); String jpqlFragment = searchFragment.getJPQLFragment(); if (searchFragment.getType() == SearchFragment.Type.PRIMARY_KEY_SUBQUERY) { jpqlFragment = " " + alias + ".id IN (" + jpqlFragment + ")"; } builder.append(jpqlFragment); } } builder.append(" ) "); return builder.toString(); } public static List<RHQLTerm> getFromAST(CommonTree tree) { List<RHQLTerm> terms = new ArrayList<RHQLTerm>(); // simple text match if (tree.getToken().getType() == RHQLLexer.IDENT) { String value = PrintUtils.collapseStringChildren(tree); RHQLTerm nextTerm = new RHQLSimpleTerm(value); terms.add(nextTerm); return terms; } // advanced query match String lineage = null; String path = null; String param = null; CommonTree contextTree = (CommonTree) tree.getChild(0); if (contextTree != null) { for (int childIndex = 0; childIndex < contextTree.getChildCount(); childIndex++) { CommonTree child = (CommonTree) contextTree.getChild(childIndex); if (child.getToken().getType() == RHQLLexer.LINEAGE) { lineage = PrintUtils.collapseStringChildren(child); } else if (child.getToken().getType() == RHQLLexer.PATH) { path = PrintUtils.collapseStringChildren(child); } else if (child.getToken().getType() == RHQLLexer.PARAM) { child = (CommonTree) child.getChild(0); // get the IDENT child of PARAM param = PrintUtils.collapseStringChildren(child); } } } else { throw new IllegalStateException("There is an issue with AST tree construction. Token " + tree.getToken().getText() + " has no children"); } String value = null; if (tree.getChildCount() > 1) { CommonTree valueTree = (CommonTree) tree.getChild(1); for (int childIndex = 0; childIndex < valueTree.getChildCount(); childIndex++) { CommonTree indentChildTree = (CommonTree) valueTree.getChild(childIndex); value = PrintUtils.collapseStringChildren(indentChildTree); int type = tree.getToken().getType(); RHQLComparisonOperator operator = getComparisonOperatorFromTokenType(type, value); RHQLTerm nextTerm = new RHQLAdvancedTerm(lineage, path, param, operator, value); terms.add(nextTerm); } } return terms; } public static RHQLComparisonOperator getComparisonOperatorFromTokenType(int tokenType, String value) { boolean nullValue = value.trim().toLowerCase().equals("null"); switch (tokenType) { case RHQLLexer.OP_EQUALS: return nullValue ? RHQLComparisonOperator.NULL : RHQLComparisonOperator.EQUALS; case RHQLLexer.OP_EQUALS_STRICT: return nullValue ? RHQLComparisonOperator.NULL : RHQLComparisonOperator.EQUALS_STRICT; case RHQLLexer.OP_NOT_EQUALS: return nullValue ? RHQLComparisonOperator.NOT_NULL : RHQLComparisonOperator.NOT_EQUALS; case RHQLLexer.OP_NOT_EQUALS_STRICT: return nullValue ? RHQLComparisonOperator.NOT_NULL : RHQLComparisonOperator.NOT_EQUALS_STRICT; case RHQLLexer.OP_LESS_THAN: return RHQLComparisonOperator.LESS_THAN; case RHQLLexer.OP_GREATER_THAN: return RHQLComparisonOperator.GREATER_THAN; default: throw new IllegalArgumentException("There is no known RHQLComparisonOperator for token type " + tokenType); } } public static RHQLTreeOperator getTreeOperatorFromTokenType(int tokenType) { switch (tokenType) { case RHQLLexer.AND: return RHQLTreeOperator.AND; case RHQLLexer.OR: return RHQLTreeOperator.OR; default: return null; } } private static class PrintUtils { public static void printJPQL(String[] tokens) { String indent = ""; List<String> lineBreakers = Arrays.asList("SELECT", "FROM", "WHERE", "AND", "OR", "(", ")"); for (String next : tokens) { if (next.equals("(")) { indent = " " + indent; } if (lineBreakers.contains(next)) { //System.out.println(); LOG.debug("indent = " + indent); } LOG.debug(next); if (next.equals(")")) { indent = indent.substring(3); } } } public static void print(CommonTree tree, String indent) { if (tree == null) { return; } /* if (tree instanceof CommonErrorNode) { CommonErrorNode errorNode = (CommonErrorNode) tree; RecognitionException error = errorNode.trappedException; int position = error.index; if (error instanceof MismatchedTokenException) { MismatchedTokenException mismatchedError = (MismatchedTokenException) error; int expecting = mismatchedError.expecting; String expectedToken = parser.getTokenNames()[expecting]; } } */ Token token = tree.getToken(); if (token == null) { return; } LOG.debug(indent + token.getText()); if (isStringNode(token)) { LOG.debug(collapseStringChildren(tree)); } else { //System.out.println(); for (int childIndex = 0; childIndex < tree.getChildCount(); childIndex++) { CommonTree child = (CommonTree) tree.getChild(childIndex); print(child, indent + " "); } } } private static boolean isStringNode(Token token) { switch (token.getType()) { case RHQLLexer.PATH: case RHQLLexer.IDENT: return true; default: return false; } } public static String collapseStringChildren(CommonTree tree) { StringBuilder builder = new StringBuilder(); for (int childIndex = 0; childIndex < tree.getChildCount(); childIndex++) { CommonTree child = (CommonTree) tree.getChild(childIndex); builder.append(child.getText()); } return builder.toString(); } } }