/******************************************************************************* * Copyright (c) 2014 BestSolution.at and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * tom <FIRSTNAME.LASTNAME@bestsolution.at> - initial API and implementation *******************************************************************************/ package at.bestsolution.persistence.java.query; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import at.bestsolution.persistence.expr.Expression; import at.bestsolution.persistence.expr.ExpressionType; import at.bestsolution.persistence.expr.GroupExpression; import at.bestsolution.persistence.expr.PropertyExpression; import at.bestsolution.persistence.expr.QueryFunction; import at.bestsolution.persistence.expr.RangeExpression.Range; import at.bestsolution.persistence.java.DatabaseSupport; import at.bestsolution.persistence.java.JavaObjectMapper; import at.bestsolution.persistence.order.OrderColumn; public abstract class DynamicBaseQuery<T,O> { private final DatabaseSupport db; public DynamicBaseQuery(DatabaseSupport db) { this.db = db; } public static class Join { public final String joinTable; public final String joinAlias; public final String joinColumn; public final String otherAlias; public final String otherColumn; public Join(String joinTable, String joinAlias, String joinColumn, String otherAlias, String otherColumn) { this.joinTable = joinTable; this.joinAlias = joinAlias; this.joinColumn = joinColumn; this.otherAlias = otherAlias; this.otherColumn = otherColumn; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((joinAlias == null) ? 0 : joinAlias.hashCode()); result = prime * result + ((joinColumn == null) ? 0 : joinColumn.hashCode()); result = prime * result + ((joinTable == null) ? 0 : joinTable.hashCode()); result = prime * result + ((otherAlias == null) ? 0 : otherAlias.hashCode()); result = prime * result + ((otherColumn == null) ? 0 : otherColumn.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Join other = (Join) obj; if (joinAlias == null) { if (other.joinAlias != null) return false; } else if (!joinAlias.equals(other.joinAlias)) return false; if (joinColumn == null) { if (other.joinColumn != null) return false; } else if (!joinColumn.equals(other.joinColumn)) return false; if (joinTable == null) { if (other.joinTable != null) return false; } else if (!joinTable.equals(other.joinTable)) return false; if (otherAlias == null) { if (other.otherAlias != null) return false; } else if (!otherAlias.equals(other.otherAlias)) return false; if (otherColumn == null) { if (other.otherColumn != null) return false; } else if (!otherColumn.equals(other.otherColumn)) return false; return true; } } protected void appendValue(List<TypedValue> rv, JavaObjectMapper<?> mapper, Expression<O> expression) { switch (expression.type) { case AND: case OR: for (Expression<O> e : ((GroupExpression<O>) expression).expressions) { appendValue(rv, mapper, e); } break; case IN: case NOT_IN: { PropertyExpression<O> e = (PropertyExpression<O>)expression; JDBCType jdbcType = mapper.getJDBCType(e.property); for( Object data : e.data ) { rv.add( new TypedValue( data instanceof EObject ? ((EObject)data).eGet(mapper.getReferenceId(e.property)) : data, jdbcType)); } break; } case IS_NOT_NULL: case IS_NULL: // skip it break; case LIKE: case ILIKE: case NOT_ILIKE: case NOT_LIKE: { PropertyExpression<O> e = (PropertyExpression<O>)expression; for( Object data : e.data ) { rv.add( new TypedValue( data instanceof EObject ? ((EObject)data).eGet(mapper.getReferenceId(e.property)) : data, JDBCType.STRING)); } break; } default: { PropertyExpression<O> e = (PropertyExpression<O>)expression; boolean hasFunctions = e.hasFunctions(); JDBCType jdbcType = mapper.getJDBCType(e.property); if( hasFunctions ) { for( QueryFunction<?, ?, ?> f : e.getFunctions() ) { jdbcType = fromJavaType(f.getValueType()); } } for( Object data : e.data ) { Object value = data instanceof EObject ? ((EObject)data).eGet(mapper.getReferenceId(e.property)) : data; if( hasFunctions ) { for( QueryFunction<O, ?, ?> f : e.getFunctions() ) { QueryFunction<O, Object, Object> ff = (QueryFunction<O, Object, Object>) f; value = ff.convert(value); } } rv.add( new TypedValue( value, jdbcType)); } break; } } } private JDBCType fromJavaType(Class<?> type) { if( type == String.class ) { return JDBCType.STRING; } else if( type == int.class || type == Integer.class ) { return JDBCType.INT; } else if( type == long.class || type == Long.class ) { return JDBCType.LONG; } else if( type == double.class || type == Double.class ) { return JDBCType.DOUBLE; } else if( type == boolean.class || type == Boolean.class ) { return JDBCType.BOOLEAN; } else if( type == float.class || type == Float.class ) { return JDBCType.FLOAT; } return JDBCType.UNKNOWN; } protected void appendOrderColumn(StringBuilder b, OrderColumn<O> column) { if( b.length() != 0 ) { b.append(","); } b.append(column.column + (column.asc ? "ASC" : "DESC")); } protected String applyCriteriaFunction(String columnExpression, QueryFunction<O, ?, ?> function) { switch (function.getType()) { case LPAD: return "lpad("+columnExpression+", "+function.getData()[0]+")"; default: break; } return columnExpression; } protected void appendJoinCriteria(LinkedHashSet<Join> joins, final JavaObjectMapper<?> mapper, final String colPrefix, final Expression<O> expression) { switch (expression.type) { case AND: case OR: { for (Expression<O> e : ((GroupExpression<O>) expression).expressions) { appendJoinCriteria(joins, mapper, colPrefix, e); } break; } default: { PropertyExpression<O> p = (PropertyExpression<O>) expression; if( p.property.contains(".") ) { String[] segments = p.property.split("\\."); String currentTableAlias = colPrefix; JavaObjectMapper<?> currentMapper = mapper; for( int i = 0; i < segments.length-1; i++ ) { JavaObjectMapper<?> oppositeMapper = currentMapper.createMapperForReference(segments[i]); String joinTable = oppositeMapper.getTableName(); String joinAlias = "emap_" +segments[i]; String joinColumn; //TODO Need to handle N:M String oldColumn; if( currentMapper.getColumnName(segments[i]) != null ) { // this means we are table with the FK joinColumn = oppositeMapper.getColumnName(currentMapper.getReferenceId(segments[i]).getName()); oldColumn = currentMapper.getColumnName(segments[i]); } else { EReference r = (EReference) currentMapper.getEClass().getEStructuralFeature(segments[i]); joinColumn = oppositeMapper.getColumnName(r.getEOpposite().getName()); oldColumn = currentMapper.getColumnName(oppositeMapper.getReferenceId(r.getEOpposite().getName()).getName()); } joins.add(new Join(joinTable, joinAlias,joinColumn,currentTableAlias.isEmpty() ? null : currentTableAlias,oldColumn)); currentMapper = oppositeMapper; currentTableAlias = joinAlias; } } break; } } } protected String getColumnExpression(JavaObjectMapper<?> mapper, String colPrefix, Expression<O> expression) { String columnExpression = null; if (expression instanceof PropertyExpression) { PropertyExpression<O> propertyExpression = (PropertyExpression<O>) expression; if (propertyExpression.property.startsWith("@")) { columnExpression = propertyExpression.property; } else if (propertyExpression.property.contains(".")) { String[] parts = propertyExpression.property.split("\\."); String lastType = "emap_" + parts[parts.length-2]; // Remark do not escape the alias columnExpression = lastType + "." + quoteColumnName(mapper.getColumnName(propertyExpression.property)); } else { columnExpression = colPrefix + quoteColumnName(mapper.getColumnName(propertyExpression.property)); } for( QueryFunction<O, ?, ?> data : propertyExpression.getFunctions() ) { columnExpression = applyCriteriaFunction(columnExpression, data); } } return columnExpression; } protected void appendCriteria(StringBuilder b, JavaObjectMapper<?> mapper, String colPrefix, Expression<O> expression) { String columnExpression = getColumnExpression(mapper, colPrefix, expression); switch (expression.type) { case AND: { b.append("("); boolean flag = false; for (Expression<O> e : ((GroupExpression<O>) expression).expressions) { if (flag) { b.append(" AND "); } appendCriteria(b, mapper, colPrefix, e); flag = true; } b.append(")"); break; } case OR: { b.append("("); boolean flag = false; for (Expression<O> e : ((GroupExpression<O>) expression).expressions) { if (flag) { b.append(" OR "); } appendCriteria(b, mapper, colPrefix, e); flag = true; } b.append(")"); break; } case EQUALS: b.append( columnExpression ); b.append(" = ?"); break; case IEQUALS: b.append( "lower( " + columnExpression + " )"); b.append(" = lower( ? )"); break; case NOT_EQUALS: b.append( columnExpression ); b.append(" <> ?"); break; case INOT_EQUALS: b.append( "lower( " + columnExpression + " )"); b.append(" <> lower( ? )"); break; case GT: b.append( columnExpression ); b.append(" > ?"); break; case GTE: b.append( columnExpression ); b.append(" >= ?"); break; case LT: b.append( columnExpression ); b.append(" < ?"); break; case LTE: b.append( columnExpression ); b.append(" <= ?"); break; case IS_NOT_NULL: b.append( columnExpression ); b.append(" IS NOT NULL"); break; case IS_NULL: b.append( columnExpression ); b.append(" IS NULL"); break; case ILIKE: b.append( columnExpression ); b.append(" ILIKE ?"); break; case LIKE: b.append( columnExpression ); b.append(" LIKE ?"); break; case NOT_ILIKE: b.append( columnExpression ); b.append(" NOT ILIKE ?"); break; case NOT_LIKE: b.append( columnExpression ); b.append(" NOT LIKE ?"); break; case IN: case NOT_IN: { String in = expression.type == ExpressionType.IN ? " IN " : " NOT IN "; //TODO We could replace with a BETWEEN or >= & <= QUERY PropertyExpression<O> propExpression = (PropertyExpression<O>)expression; b.append( columnExpression ); b.append(" "+in+" ( "); boolean flag = false; for( int i = 0; i < propExpression.data.size(); i++ ) { if( flag ) { b.append(","); } flag = true; b.append("?"); } b.append(" )"); break; } case RANGE: { PropertyExpression<O> propExpression = (PropertyExpression<O>)expression; List<Object> invalues = new ArrayList<Object>(); List<String> betweenSegements = new ArrayList<String>(); for( Object data : propExpression.data ) { Range r = (Range) data; if( r.min.endsWith(r.max) ) { invalues.add(r.min); } else { betweenSegements.add( columnExpression + " >= ? AND " + columnExpression + " <= ?" ); } } JDBCType jdbcType = mapper.getJDBCType(propExpression.property); if( ! invalues.isEmpty() ) { } } default: break; } } protected String quoteColumnName(String columnName) { return '"' + (db.isDefaultLowerCase() ? columnName.toLowerCase() : columnName.toUpperCase()) + '"'; } }