package com.temenos.interaction.jdbc.producer.sql; import java.util.ArrayList; import java.util.List; import javax.ws.rs.core.Response.Status; import com.temenos.interaction.jdbc.SqlRelation; import com.temenos.interaction.jdbc.exceptions.JdbcException; /* * Class containing a SQL command stored as a tree. This is required because OData4j Expression calls it's * visitor in a different sequence to that needed for construction of the SQL command. */ /* * #%L * interaction-jdbc-producer * %% * Copyright (C) 2012 - 2013 Temenos Holdings N.V. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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 Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ public class SQLExpressionNode { // Node will either contain a relation or a list of arguments. private List<String> arguments = new ArrayList<String>(); private SqlRelation rel; // Flag indicating term is bracketed. private boolean isBracketed; // Flag indicating SQL term should be surrounded by spaces. private boolean isSpaced; // Parent node of this node. private SQLExpressionNode parent; // Maximum number of arguments for a node private static int MAX_ARGUMENTS = 3; // Constructor for the root node. public SQLExpressionNode() { this(null); } // Constructor for child nodes. public SQLExpressionNode(SQLExpressionNode parent) { this.parent = parent; isBracketed = false; } /* * Method to print a complete node as an SQL command. */ public String toSqlParameter() { StringBuilder sb = new StringBuilder(); if (isBracketed) { appendOpenBracket(sb); } if ((null != rel) && (arguments.isEmpty())) { throw new JdbcException(Status.INTERNAL_SERVER_ERROR, "Node has no content."); } if (null != rel) { // First try to add a relation if (!rel.getSqlSymbol().contains("%s")) { // It's a regular function or operator if (isFunction()) { appendFunction(sb); } else { appendOperator(sb); } } else { // It's an SQL operation that cannot be represented as a // simple function or operator. appendFormatString(sb); } } else { // Add the arguments. boolean first = true; for (String argument : arguments) { first = appendFunctionArgument(sb, argument, first); } } if (isBracketed) { appendCloseBracket(sb); } // Remove any trailing spaces String str = sb.toString().trim(); return str; } private void appendFunction(StringBuilder sb) { appendSymbol(sb); appendOpenBracket(sb); appendFunctionArguments(sb); appendCloseBracket(sb); } private void appendFunctionArguments(StringBuilder sb) { boolean first = true; if (null == rel.getArgumentSequence()) { // Add SQL arguments in same order as oData. for (String argument : arguments) { first = appendFunctionArgument(sb, argument, first); } } else { // Add SQL arguments in specified order. appendOrderedFunctionArguments(sb); } } private void appendOrderedFunctionArguments(StringBuilder sb) { boolean first = true; for (Integer i : rel.getArgumentSequence()) { if (i >= arguments.size()) { throw new JdbcException(Status.INTERNAL_SERVER_ERROR, "Argument index \"" + i + "\" too big."); } else { first = appendFunctionArgument(sb, arguments.get(i), first); } } } private boolean appendFunctionArgument(StringBuilder sb, String argument, boolean first) { boolean firstReturn = first; if (firstReturn) { firstReturn = false; } else { appendCommaSpace(sb); } sb.append(argument); return firstReturn; } /* * There are some SQL operations that cannot be represented as an operator * (e.g. 'a eq b') or as a function (e.g. 'func(a, b...')). In this case we * have a formatted sting into which the arguments must be inserted. */ private void appendFormatString(StringBuilder sb) { sb.append(String.format(rel.getSqlSymbol(), arguments.toArray())); } private void appendOperator(StringBuilder sb) { switch (arguments.size()) { case 0: // Just append self appendValue(sb); break; case 1: appendUnaryOperator(sb); break; case 2: appendBinaryOperator(sb); break; default: throw new JdbcException(Status.INTERNAL_SERVER_ERROR, "Too many arguments for an operator \"" + rel.getSqlSymbol() + "\"."); } } private void appendBinaryOperator(StringBuilder sb) { sb.append(arguments.get(0)); if (isSpaced()) { appendSingleSpace(sb); } if (null != rel) { appendSymbol(sb); if (isSpaced()) { appendSingleSpace(sb); } } sb.append(arguments.get(1)); } private void appendSymbol(StringBuilder sb) { sb.append(rel.getSqlSymbol()); } private void appendUnaryOperator(StringBuilder sb) { if (null != rel) { appendSymbol(sb); if (isSpaced()) { appendSingleSpace(sb); } } sb.append(arguments.get(0)); } private void appendValue(StringBuilder sb) { // Add the first argument. sb.append(arguments.get(0)); } /* * Add space if not already present. */ private void appendSingleSpace(StringBuilder sb) { if ((0 < sb.length()) && !(' ' == sb.charAt(sb.length() - 1))) { appendSpace(sb); } } private void appendCommaSpace(StringBuilder sb) { appendComma(sb); appendSpace(sb); } private void appendOpenBracket(StringBuilder sb) { sb.append("("); } private void appendCloseBracket(StringBuilder sb) { sb.append(")"); } private void appendSpace(StringBuilder sb) { sb.append(" "); } private void appendComma(StringBuilder sb) { sb.append(","); } /* * Get parent node. The root returns null. */ public SQLExpressionNode getParent() { return parent; } public boolean addArgument(String argument) { if (MAX_ARGUMENTS <= arguments.size()) { throw new JdbcException(Status.INTERNAL_SERVER_ERROR, "Too many argumentents for function or operator."); } arguments.add(argument); return true; } public boolean setRelation(SqlRelation rel) { if (null != this.rel) { throw new JdbcException(Status.INTERNAL_SERVER_ERROR, "Relation already set."); } this.rel = rel; return true; } public void setIsBracketed() { isBracketed = true; } public boolean isBracketed() { return isBracketed; } public void setIsSpaced(boolean spaced) { this.isSpaced = spaced; } public boolean isFunction() { if (null == rel) { // If there is no relation it's not a functions. return false; } return rel.isFunctionCall(); } public boolean isSpaced() { return isSpaced; } }