/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.translator.odata4;
import static org.teiid.language.SQLConstants.Reserved.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
import org.teiid.language.*;
import org.teiid.language.SQLConstants.Tokens;
import org.teiid.language.visitor.HierarchyVisitor;
import org.teiid.metadata.Column;
import org.teiid.metadata.FunctionMethod;
import org.teiid.metadata.RuntimeMetadata;
import org.teiid.metadata.Table;
import org.teiid.olingo.common.ODataTypeManager;
import org.teiid.translator.SourceSystemFunctions;
import org.teiid.translator.TranslatorException;
/**
* Only walks the filter version of the query
*/
public class ODataFilterVisitor extends HierarchyVisitor {
private static Map<String, String> infixFunctions = new HashMap<String, String>();
static {
infixFunctions.put("%", "mod");//$NON-NLS-1$ //$NON-NLS-2$
infixFunctions.put("+", "add");//$NON-NLS-1$ //$NON-NLS-2$
infixFunctions.put("-", "sub");//$NON-NLS-1$ //$NON-NLS-2$
infixFunctions.put("*", "mul");//$NON-NLS-1$ //$NON-NLS-2$
infixFunctions.put("/", "div");//$NON-NLS-1$ //$NON-NLS-2$
}
protected StringBuilder filter = new StringBuilder();
private ODataExecutionFactory ef;
private Stack<String> exprType = new Stack<String>();
protected ArrayList<TranslatorException> exceptions = new ArrayList<TranslatorException>();
private ODataQuery query;
private RuntimeMetadata metadata;
private ODataDocumentNode filterOnElement;
public ODataFilterVisitor(ODataExecutionFactory ef, RuntimeMetadata metadata, ODataQuery query) {
this.ef = ef;
this.query = query;
this.metadata = metadata;
}
public void appendFilter(Condition condition) throws TranslatorException{
append(condition);
if (!this.exceptions.isEmpty()) {
throw this.exceptions.get(0);
}
if (this.filter.length() > 0) {
this.filterOnElement.addFilter(this.filter.toString());
}
}
@Override
public void visit(Comparison obj) {
append(obj.getLeftExpression());
this.filter.append(Tokens.SPACE);
switch(obj.getOperator()) {
case EQ:
this.filter.append("eq"); //$NON-NLS-1$
break;
case NE:
this.filter.append("ne"); //$NON-NLS-1$
break;
case LT:
this.filter.append("lt"); //$NON-NLS-1$
break;
case LE:
this.filter.append("le"); //$NON-NLS-1$
break;
case GT:
this.filter.append("gt"); //$NON-NLS-1$
break;
case GE:
this.filter.append("ge"); //$NON-NLS-1$
break;
}
this.filter.append(Tokens.SPACE);
appendRightComparison(obj);
}
protected void appendRightComparison(Comparison obj) {
append(obj.getRightExpression());
}
@Override
public void visit(IsNull obj) {
if (obj.isNegated()) {
this.filter.append(NOT.toLowerCase()).append(Tokens.LPAREN);
}
appendNested(obj.getExpression());
this.filter.append(Tokens.SPACE);
this.filter.append("eq").append(Tokens.SPACE); //$NON-NLS-1$
this.filter.append(NULL.toLowerCase());
if (obj.isNegated()) {
this.filter.append(Tokens.RPAREN);
}
}
private void appendNested(Expression ex) {
boolean useParens = ex instanceof Condition;
if (useParens) {
this.filter.append(Tokens.LPAREN);
}
append(ex);
if (useParens) {
this.filter.append(Tokens.RPAREN);
}
}
@Override
public void visit(AndOr obj) {
String opString = obj.getOperator().name().toLowerCase();
appendNestedCondition(obj, obj.getLeftCondition());
this.filter.append(Tokens.SPACE)
.append(opString)
.append(Tokens.SPACE);
appendNestedCondition(obj, obj.getRightCondition());
}
protected void appendNestedCondition(AndOr parent, Condition condition) {
if (condition instanceof AndOr) {
AndOr nested = (AndOr)condition;
if (nested.getOperator() != parent.getOperator()) {
this.filter.append(Tokens.LPAREN);
append(condition);
this.filter.append(Tokens.RPAREN);
return;
}
}
append(condition);
}
private String odataType(String type, String runtimeType) {
if (type == null) {
type = ODataTypeManager.odataType(runtimeType)
.getFullQualifiedName().getFullQualifiedNameAsString();
}
return type;
}
@Override
public void visit(ColumnReference obj) {
Column column = obj.getMetadataObject();
// check if the column on psedo column, then move it to the parent.
String pseudo = ODataMetadataProcessor.getPseudo(column);
this.exprType.push(odataType(column.getNativeType(), column.getRuntimeType()));
ODataDocumentNode schemaElement = this.query.getSchemaElement((Table)column.getParent());
if (pseudo != null) {
try {
Table columnParent = (Table)column.getParent();
Table pseudoColumnParent = this.metadata.getTable(
ODataMetadataProcessor.getMerge(columnParent));
schemaElement = this.query.getSchemaElement(pseudoColumnParent);
} catch (TranslatorException e) {
this.exceptions.add(e);
}
}
if (this.filterOnElement == null) {
this.filterOnElement = schemaElement;
} else if (schemaElement.isExpandType() && (!this.filterOnElement.isExpandType())) {
this.exceptions.add(new TranslatorException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17026)));
}
if (this.filterOnElement.isComplexType()) {
if (pseudo == null) {
this.filter.append(this.filterOnElement.getName()).append("/").append(column.getName());
} else {
this.filter.append(pseudo);
}
} else {
if (pseudo == null) {
this.filter.append(column.getName());
} else {
this.filter.append(pseudo);
}
}
}
protected boolean isInfixFunction(String function) {
return infixFunctions.containsKey(function);
}
@Override
public void visit(Function obj) {
if (this.ef.getFunctionModifiers().containsKey(obj.getName())) {
this.ef.getFunctionModifiers().get(obj.getName()).translate(obj);
}
String name = obj.getName();
List<Expression> args = obj.getParameters();
if(isInfixFunction(name)) {
this.filter.append(Tokens.LPAREN);
if(args != null) {
for(int i=0; i<args.size(); i++) {
append(args.get(i));
if(i < (args.size()-1)) {
this.filter.append(Tokens.SPACE);
this.filter.append(infixFunctions.get(name));
this.filter.append(Tokens.SPACE);
}
}
}
this.filter.append(Tokens.RPAREN);
}
else {
FunctionMethod method = obj.getMetadataObject();
if (name.startsWith(method.getCategory())) {
name = name.substring(method.getCategory().length()+1);
}
this.filter.append(name).append(Tokens.LPAREN);
if (name.equals("cast")) {
append(args.get(0));
this.filter.append(Tokens.COMMA);
Literal literal = (Literal)args.get(1);
String type = ODataTypeManager
.odataType((String) literal.getValue())
.getFullQualifiedName().getFullQualifiedNameAsString();
this.filter.append(type);
this.exprType.push(type);
} else {
if (args != null && args.size() != 0) {
if (SourceSystemFunctions.ENDSWITH.equalsIgnoreCase(name)) {
append(args.get(1));
this.filter.append(Tokens.COMMA);
append(args.get(0));
} else {
for (int i = 0; i < args.size(); i++) {
append(args.get(i));
if (i < args.size()-1) {
this.filter.append(Tokens.COMMA);
}
}
}
}
this.exprType.push(odataType(
method.getOutputParameter().getNativeType(),
method.getOutputParameter().getRuntimeType()));
}
this.filter.append(Tokens.RPAREN);
}
}
public void visit(In obj) {
for (int i = 0; i < obj.getRightExpressions().size(); i++) {
Expression expr = obj.getRightExpressions().get(i);
if (i != 0) {
this.filter.append(" or ");
}
visitNode(obj.getLeftExpression());
this.filter.append(" eq ");
visitNode(expr);
}
}
@Override
public void visit(Literal obj) {
try {
String odataType = this.exprType.pop();
this.filter.append(ODataTypeManager.convertToODataURIValue(obj.getValue(), odataType));
} catch (EdmPrimitiveTypeException e) {
this.exceptions.add(new TranslatorException(e));
}
}
@Override
public void visit(Not obj) {
this.filter.append(NOT)
.append(Tokens.SPACE)
.append(Tokens.LPAREN);
append(obj.getCriteria());
this.filter.append(Tokens.RPAREN);
}
public void append(LanguageObject obj) {
visitNode(obj);
}
protected void append(List<? extends LanguageObject> items) {
if (items != null && items.size() != 0) {
for (int i = 0; i < items.size(); i++) {
append(items.get(i));
}
}
}
protected void append(LanguageObject[] items) {
if (items != null && items.length != 0) {
for (int i = 0; i < items.length; i++) {
append(items[i]);
}
}
}
}