/*
* 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.salesforce.execution.visitors;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import org.teiid.core.util.TimestampWithTimezone;
import org.teiid.language.*;
import org.teiid.language.Comparison.Operator;
import org.teiid.language.visitor.HierarchyVisitor;
import org.teiid.metadata.Column;
import org.teiid.metadata.RuntimeMetadata;
import org.teiid.metadata.Table;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.salesforce.Constants;
import org.teiid.translator.salesforce.SalesForcePlugin;
import org.teiid.translator.salesforce.Util;
/**
* Parses Criteria in support of all of the ExecutionImpl classes.
*/
public class CriteriaVisitor extends HierarchyVisitor implements ICriteriaVisitor {
private static final double SCIENTIFIC_LOW = Math.pow(10, -3);
private static final double SCIENTIFIC_HIGH = Math.pow(10, 7);
private static final String RESTRICTEDMULTISELECTPICKLIST = "restrictedmultiselectpicklist"; //$NON-NLS-1$
private static final String MULTIPICKLIST = "multipicklist"; //$NON-NLS-1$
protected static final String SELECT = "SELECT"; //$NON-NLS-1$
protected static final String FROM = "FROM"; //$NON-NLS-1$
protected static final String WHERE = "WHERE"; //$NON-NLS-1$
protected static final String ORDER_BY = "ORDER BY"; //$NON-NLS-1$
protected static final String LIMIT = "LIMIT"; //$NON-NLS-1$
protected static final String SPACE = " "; //$NON-NLS-1$
protected static final String EXCLUDES = "EXCLUDES"; //$NON-NLS-1$
protected static final String INCLUDES = "includes"; //$NON-NLS-1$
protected static final String COMMA = ","; //$NON-NLS-1$
protected static final String SEMI = ";"; //$NON-NLS-1$
protected static final String APOS = "'"; //$NON-NLS-1$
protected static final String OPEN = "("; //$NON-NLS-1$
protected static final String CLOSE = ")"; //$NON-NLS-1$
protected RuntimeMetadata metadata;
//buffer of criteria parts
protected List<String> criteriaBuffer = new ArrayList<String>();
protected boolean hasCriteria;
protected List<TranslatorException> exceptions = new ArrayList<TranslatorException>();
protected Table table;
boolean onlyIDCriteria;
protected boolean queryAll = Boolean.FALSE;
// support for invoking a retrieve when possible.
protected In idInCriteria = null;
public CriteriaVisitor( RuntimeMetadata metadata ) {
this.metadata = metadata;
}
@Override
public void visit( Comparison criteria ) {
addCompareCriteria(criteria);
boolean isAcceptableID = (Operator.EQ == criteria.getOperator() && isIdColumn(criteria.getLeftExpression()));
setHasCriteria(true, isAcceptableID);
if (isAcceptableID) {
this.idInCriteria = new In(criteria.getLeftExpression(), Arrays.asList(criteria.getRightExpression()), false);
}
}
public void visit(IsNull obj) {
visit(new Comparison(obj.getExpression(), new Literal(null, obj.getExpression().getType()), obj.isNegated()?Comparison.Operator.NE:Comparison.Operator.EQ));
}
@Override
public void visit( Like criteria ) {
if (isIdColumn(criteria.getLeftExpression())) {
TranslatorException e = new TranslatorException(SalesForcePlugin.Util.getString("CriteriaVisitor.LIKE.not.supported.on.Id")); //$NON-NLS-1$
exceptions.add(e);
}
if (isMultiSelectColumn(criteria.getLeftExpression())) {
TranslatorException e = new TranslatorException(SalesForcePlugin.Util.getString("CriteriaVisitor.LIKE.not.supported.on.multiselect")); //$NON-NLS-1$
exceptions.add(e);
}
boolean negated = criteria.isNegated();
criteria.setNegated(false);
if (negated) {
criteriaBuffer.add("NOT ("); //$NON-NLS-1$
}
criteriaBuffer.add(criteria.toString());
if (negated) {
criteriaBuffer.add(CLOSE);
criteria.setNegated(true);
}
// don't check if it's ID, Id LIKE '123%' still requires a query
setHasCriteria(true, false);
}
@Override
public void visit(AndOr obj) {
this.criteriaBuffer.add(OPEN);
super.visitNode(obj.getLeftCondition());
this.criteriaBuffer.add(CLOSE);
this.criteriaBuffer.add(SPACE);
this.criteriaBuffer.add(obj.getOperator().toString());
this.criteriaBuffer.add(SPACE);
this.criteriaBuffer.add(OPEN);
super.visitNode(obj.getRightCondition());
this.criteriaBuffer.add(CLOSE);
}
@Override
public void visit(Not obj) {
criteriaBuffer.add("NOT ("); //$NON-NLS-1$
super.visit(obj);
criteriaBuffer.add(CLOSE);
}
@Override
public void visit( In criteria ) {
Expression lExpr = criteria.getLeftExpression();
if (lExpr instanceof ColumnReference) {
ColumnReference cr = (ColumnReference)lExpr;
Column column = cr.getMetadataObject();
if (column != null && (MULTIPICKLIST.equalsIgnoreCase(column.getNativeType()) || RESTRICTEDMULTISELECTPICKLIST.equalsIgnoreCase(column.getNativeType()))) {
appendMultiselectIn(column, criteria);
} else {
appendCriteria(criteria);
}
} else {
appendCriteria(criteria);
}
setHasCriteria(true, isIdColumn(criteria.getLeftExpression()));
}
public void parseFunction( Function func ) {
String functionName = func.getName();
try {
if (functionName.equalsIgnoreCase("includes")) { //$NON-NLS-1$
generateMultiSelect(func, INCLUDES);
} else if (functionName.equalsIgnoreCase("excludes")) { //$NON-NLS-1$
generateMultiSelect(func, EXCLUDES);
}
} catch (TranslatorException e) {
exceptions.add(e);
}
}
private void generateMultiSelect( Function func,
String funcName ) throws TranslatorException {
List<Expression> expressions = func.getParameters();
validateFunction(expressions);
Expression columnExpression = expressions.get(0);
Column column = ((ColumnReference)columnExpression).getMetadataObject();
criteriaBuffer.add(column.getSourceName());
criteriaBuffer.add(SPACE);
criteriaBuffer.add(funcName);
criteriaBuffer.add(OPEN);
String fullParam = ((Literal)expressions.get(1)).toString();
String[] params = fullParam.split(","); //$NON-NLS-1$
for (int i = 0; i < params.length; i++) {
String token = params[i];
if (i != 0) {
criteriaBuffer.add(COMMA);
}
criteriaBuffer.add(Util.addSingleQuotes(token));
}
criteriaBuffer.add(CLOSE);
}
private void appendMultiselectIn( Column column,
In criteria ) {
criteriaBuffer.add(column.getSourceName());
criteriaBuffer.add(SPACE);
if (criteria.isNegated()) {
criteriaBuffer.add(EXCLUDES);
} else {
criteriaBuffer.add(INCLUDES);
}
criteriaBuffer.add(OPEN);
List<Expression> rightExpressions = criteria.getRightExpressions();
Iterator<Expression> iter = rightExpressions.iterator();
while (iter.hasNext()) {
Expression rightExpression = iter.next();
criteriaBuffer.add(rightExpression.toString());
if (iter.hasNext()) {
criteriaBuffer.add(COMMA);
}
}
criteriaBuffer.add(CLOSE);
}
private void validateFunction( List<Expression> expressions ) throws TranslatorException {
if (expressions.size() != 2) {
throw new TranslatorException(SalesForcePlugin.Util.getString("CriteriaVisitor.invalid.arg.count")); //$NON-NLS-1$
}
if (!(expressions.get(0) instanceof ColumnReference)) {
throw new TranslatorException(SalesForcePlugin.Util.getString("CriteriaVisitor.function.not.column.arg")); //$NON-NLS-1$
}
if (!(expressions.get(1) instanceof Literal)) {
throw new TranslatorException(SalesForcePlugin.Util.getString("CriteriaVisitor.function.not.literal.arg")); //$NON-NLS-1$
}
}
protected void addCompareCriteria(Comparison compCriteria ) {
Expression lExpr = compCriteria.getLeftExpression();
if (lExpr instanceof Function) {
parseFunction((Function)lExpr);
} else {
criteriaBuffer.add(getValue(lExpr, false));
criteriaBuffer.add(SPACE);
criteriaBuffer.add(compCriteria.getOperator()==Operator.NE?"!=":compCriteria.getOperator().toString()); //$NON-NLS-1$
criteriaBuffer.add(SPACE);
Expression rExp = compCriteria.getRightExpression();
criteriaBuffer.add(getValue(rExp, false));
if (lExpr instanceof ColumnReference && "IsDeleted".equalsIgnoreCase(((ColumnReference)lExpr).getMetadataObject().getSourceName())) { //$NON-NLS-1$
Literal isDeletedLiteral = (Literal)compCriteria.getRightExpression();
Boolean isDeleted = (Boolean)isDeletedLiteral.getValue();
if (isDeleted) {
this.queryAll = isDeleted;
}
}
}
}
void appendColumnReference(StringBuilder queryString,
ColumnReference ref) {
queryString.append(ref.getMetadataObject().getParent().getSourceName());
queryString.append('.');
queryString.append(ref.getMetadataObject().getSourceName());
}
private void appendCriteria( In criteria ) {
Expression leftExp = criteria.getLeftExpression();
if(isIdColumn(leftExp)) {
idInCriteria = criteria;
}
criteriaBuffer.add(getValue(leftExp, false));
criteriaBuffer.add(SPACE);
if (criteria.isNegated()) {
criteriaBuffer.add("NOT "); //$NON-NLS-1$
}
criteriaBuffer.add("IN"); //$NON-NLS-1$
criteriaBuffer.add(OPEN);
Iterator<Expression> iter = criteria.getRightExpressions().iterator();
while (iter.hasNext()) {
criteriaBuffer.add(getValue(iter.next(), false));
if (iter.hasNext()) {
criteriaBuffer.add(COMMA);
}
}
criteriaBuffer.add(CLOSE);
}
protected String getValue( Expression expr, boolean raw) {
StringBuilder result = new StringBuilder();
if (expr instanceof ColumnReference) {
appendColumnReference(result, (ColumnReference)expr);
} else if (expr instanceof Literal) {
Literal literal = (Literal)expr;
if (literal.getValue() == null) {
if (raw) {
return null;
}
return "NULL"; //$NON-NLS-1$
}
if (raw) {
return literal.getValue().toString();
}
appendLiteralValue(result, literal);
} else if (expr instanceof AggregateFunction) {
appendAggregateFunction(result, (AggregateFunction)expr);
} else {
throw new RuntimeException("unknown type in SalesforceQueryExecution.getValue(): " + expr.toString()); //$NON-NLS-1$
}
return result.toString();
}
public static void appendLiteralValue(StringBuilder result, Literal literal) {
if (literal.getValue().getClass().equals(Boolean.class)) {
result.append(((Boolean)literal.getValue()).toString());
} else if (literal.getValue().getClass().equals(java.sql.Timestamp.class)) {
Timestamp datetime = (java.sql.Timestamp)literal.getValue();
String value = datetime.toString();
int fractionalPlace = value.lastIndexOf('.');
int fractionalLength = value.length() - fractionalPlace - 1;
if (fractionalLength > 3) {
value = value.substring(0, fractionalPlace + 3);
} else if (fractionalLength < 3) {
value += "00".substring(fractionalLength - 1); //$NON-NLS-1$
}
result.append(value).setCharAt(result.length()-value.length()+10, 'T');
Calendar c = TimestampWithTimezone.getCalendar();
c.setTime(datetime);
int minutes = (c.get(Calendar.ZONE_OFFSET) +
c.get(Calendar.DST_OFFSET)) / 60000;
int val = minutes/60;
result.append(String.format("%1$+03d", val)); //$NON-NLS-1$
result.append(':');
val = minutes%60;
result.append(val/10);
result.append(val%10);
} else if (literal.getValue().getClass().equals(java.sql.Time.class)) {
result.append(literal.getValue()).append(".000").append(Util.getDefaultTimeZoneString()); //$NON-NLS-1$
} else if (literal.getValue().getClass().equals(java.sql.Date.class)) {
result.append(literal.getValue());
} else if (literal.getValue() instanceof Double) {
Double doubleVal = (Double)literal.getValue();
double value = Math.abs(doubleVal.doubleValue());
if (value <= SCIENTIFIC_LOW || value >= SCIENTIFIC_HIGH) {
result.append(BigDecimal.valueOf(doubleVal).toPlainString());
} else {
result.append(literal.toString());
}
} else if (literal.getValue() instanceof Float) {
Float floatVal = (Float)literal.getValue();
float value = Math.abs(floatVal);
if (value <= SCIENTIFIC_LOW || value >= SCIENTIFIC_HIGH) {
result.append(BigDecimal.valueOf(floatVal).toPlainString());
} else {
result.append(literal.toString());
}
} else if (literal.getValue() instanceof BigDecimal) {
result.append(((BigDecimal)literal.getValue()).toPlainString());
} else {
result.append(literal.toString());
}
}
protected void appendAggregateFunction(StringBuilder result,
AggregateFunction af) {
if (af.getName().equalsIgnoreCase(SQLConstants.NonReserved.COUNT)
&& (af.getExpression() == null || af.getExpression() instanceof Literal)) {
result.append("COUNT(Id)"); //$NON-NLS-1$
} else {
result.append(af.getName() + "(" + getValue(af.getExpression(), false) + ")"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
protected void loadColumnMetadata( NamedTable group ) throws TranslatorException {
table = group.getMetadataObject();
String supportsQuery = table.getProperty(Constants.SUPPORTS_QUERY, true);
if (supportsQuery != null && !Boolean.valueOf(supportsQuery)) {
throw new TranslatorException(table.getSourceName() + " " + SalesForcePlugin.Util.getString("CriteriaVisitor.query.not.supported")); //$NON-NLS-1$ //$NON-NLS-2$
}
List<Column> columnIds = table.getColumns();
for (Column element : columnIds) {
// influences queryAll behavior
if (element.getSourceName().equals("IsDeleted")) { //$NON-NLS-1$
String isDeleted = element.getDefaultValue();
if (Boolean.parseBoolean(isDeleted)) {
this.queryAll = true;
}
}
}
}
protected boolean isIdColumn( Expression expression ) {
boolean result = false;
if (expression instanceof ColumnReference) {
Column element = ((ColumnReference)expression).getMetadataObject();
String nameInSource = element.getSourceName();
if (nameInSource.equalsIgnoreCase("id")) { //$NON-NLS-1$
result = true;
}
}
return result;
}
protected boolean isMultiSelectColumn( Expression expression ) {
boolean result = false;
if (expression instanceof ColumnReference) {
Column element = ((ColumnReference)expression).getMetadataObject();
String nativeType = element.getNativeType();
if (MULTIPICKLIST.equalsIgnoreCase(nativeType) || RESTRICTEDMULTISELECTPICKLIST.equalsIgnoreCase(nativeType)) {
result = true;
}
}
return result;
}
public boolean hasCriteria() {
return hasCriteria;
}
public void setHasCriteria( boolean hasCriteria,
boolean isIdCriteria ) {
if (isIdCriteria) {
if (hasCriteria()) {
this.onlyIDCriteria = false;
} else {
this.onlyIDCriteria = true;
}
} else if (this.onlyIDCriteria) {
this.onlyIDCriteria = false;
}
this.hasCriteria = hasCriteria;
}
public boolean hasOnlyIDCriteria() {
return this.onlyIDCriteria;
}
public String getTableName() {
return table.getSourceName();
}
protected void addCriteriaString(StringBuilder result) {
addCriteriaString(WHERE, result);
}
protected void addCriteriaString(String clause, StringBuilder result) {
if(!criteriaBuffer.isEmpty()) {
result.append(clause).append(SPACE);
for (String string : criteriaBuffer) {
result.append(string);
}
result.append(SPACE);
}
}
}