package com.mysema.rdfbean.query; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Stack; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import com.mysema.query.BooleanBuilder; import com.mysema.query.DefaultQueryMetadata; import com.mysema.query.JoinExpression; import com.mysema.query.QueryMetadata; import com.mysema.query.types.Constant; import com.mysema.query.types.ConstantImpl; import com.mysema.query.types.Expression; import com.mysema.query.types.ExpressionUtils; import com.mysema.query.types.FactoryExpression; import com.mysema.query.types.Operation; import com.mysema.query.types.OperationImpl; import com.mysema.query.types.Operator; import com.mysema.query.types.Ops; import com.mysema.query.types.OrderSpecifier; import com.mysema.query.types.ParamExpression; import com.mysema.query.types.Path; import com.mysema.query.types.PathImpl; import com.mysema.query.types.PathMetadata; import com.mysema.query.types.PathType; import com.mysema.query.types.Predicate; import com.mysema.query.types.PredicateOperation; import com.mysema.query.types.SubQueryExpression; import com.mysema.query.types.SubQueryExpressionImpl; import com.mysema.query.types.TemplateExpression; import com.mysema.query.types.TemplateExpressionImpl; import com.mysema.query.types.Templates; import com.mysema.query.types.ToStringVisitor; import com.mysema.query.types.Visitor; import com.mysema.query.types.expr.Param; import com.mysema.query.types.expr.Wildcard; import com.mysema.query.types.template.BooleanTemplate; import com.mysema.rdfbean.CORE; import com.mysema.rdfbean.model.Block; import com.mysema.rdfbean.model.Blocks; import com.mysema.rdfbean.model.BooleanQuery; import com.mysema.rdfbean.model.ID; import com.mysema.rdfbean.model.InferenceOptions; import com.mysema.rdfbean.model.LID; import com.mysema.rdfbean.model.LIT; import com.mysema.rdfbean.model.NODE; import com.mysema.rdfbean.model.QID; import com.mysema.rdfbean.model.QLIT; import com.mysema.rdfbean.model.QNODE; import com.mysema.rdfbean.model.QueryOptions; import com.mysema.rdfbean.model.RDF; import com.mysema.rdfbean.model.RDFConnection; import com.mysema.rdfbean.model.RDFQueryImpl; import com.mysema.rdfbean.model.TupleQuery; import com.mysema.rdfbean.model.UID; import com.mysema.rdfbean.model.XSD; import com.mysema.rdfbean.object.Configuration; import com.mysema.rdfbean.object.MappedClass; import com.mysema.rdfbean.object.MappedPath; import com.mysema.rdfbean.object.MappedPredicate; import com.mysema.rdfbean.object.MappedProperty; import com.mysema.rdfbean.object.Session; import com.mysema.rdfbean.ontology.Ontology; import com.mysema.rdfbean.xsd.ConverterRegistry; /** * @author tiwe * */ public class RDFQueryBuilder implements Visitor<Object, Filters> { private static final Logger logger = LoggerFactory.getLogger(RDFQueryBuilder.class); private static final Templates TEMPLATES = new Templates() { { add(PathType.PROPERTY, "{0}_{1}"); add(PathType.COLLECTION_ANY, "{0}"); add(PathType.LISTVALUE_CONSTANT, "{0}_{1}"); add(PathType.ARRAYVALUE_CONSTANT, "{0}_{1}"); add(PathType.MAPVALUE_CONSTANT, "{0}_{1}"); } }; private static final Path<LIT> COUNTER = new PathImpl<LIT>(LIT.class, "counter"); private final RDFConnection connection; private final Session session; private final Configuration configuration; private final Ontology ontology; private final QueryOptions queryOptions; private final InferenceOptions inferenceOptions; private final QueryMetadata metadata; private final List<Expression<?>> projection; private final VarNameIterator varNames = new VarNameIterator("_var_"); private final Stack<Operator<?>> operatorStack = new Stack<Operator<?>>(); private final Map<Path<?>, UID> pathToContext = new HashMap<Path<?>, UID>(); private final Map<ParamExpression<?>, Object> params = new HashMap<ParamExpression<?>, Object>(); private Map<Path<?>, ParamExpression<?>> pathToMapped = new HashMap<Path<?>, ParamExpression<?>>(); private Map<Path<?>, ParamExpression<?>> pathToKnown = new HashMap<Path<?>, ParamExpression<?>>(); public RDFQueryBuilder(RDFConnection connection, Session session, Configuration configuration, Ontology ontology, QueryMetadata metadata) { this.connection = connection; this.session = session; this.configuration = configuration; this.ontology = ontology; this.queryOptions = connection.getQueryOptions(); this.inferenceOptions = connection.getInferenceOptions(); this.metadata = metadata; this.projection = new ArrayList<Expression<?>>(); } @SuppressWarnings("unchecked") RDFQueryImpl build(boolean forCount) { RDFQueryImpl query = new RDFQueryImpl(connection); Filters filters = new Filters(); // from for (JoinExpression join : metadata.getJoins()) { query.where(handleRootPath((Path<?>) join.getTarget(), filters)); } // where if (metadata.getWhere() != null) { query.where(transform(metadata.getWhere(), filters)); } // group by for (Expression<?> expr : metadata.getGroupBy()) { query.groupBy(transform(expr, filters)); } // having if (metadata.getHaving() != null) { query.having(transform(metadata.getHaving(), filters)); } filters.beginOptional(); if (forCount) { projection.add(queryOptions.isCountViaAggregation() ? Wildcard.count : COUNTER); } else { // order by (optional paths) for (OrderSpecifier<?> os : metadata.getOrderBy()) { query.orderBy(transform(filters, os)); } // limit + offset query.restrict(metadata.getModifiers()); // select (optional paths); for (Expression<?> expr : metadata.getProjection()) { if (expr instanceof FactoryExpression) { FactoryExpression<?> fe = (FactoryExpression<?>) expr; for (Expression<?> e : fe.getArgs()) { projection.add(transform(e, filters)); } } else { projection.add(transform(expr, filters)); } } } filters.endOptional(); query.where(filters.toArray()); if (metadata.isDistinct()) { query.distinct(); } for (Map.Entry<ParamExpression<?>, Object> entry : params.entrySet()) { query.set((Param) entry.getKey(), entry.getValue()); } return query; } public BooleanQuery createBooleanQuery() { return build(false).createBooleanQuery(); } public TupleQuery createTupleQuery(boolean forCount) { return build(forCount).createTupleQuery(projection.toArray(new Expression[projection.size()])); } @Nullable private UID getContext(Path<?> path) { if (pathToContext.containsKey(path)) { return pathToContext.get(path); } else if (path.getMetadata().getParent() != null) { return getContext(path.getMetadata().getParent()); } else { return null; } } private MappedPath getMappedPath(Path<?> path) { PathMetadata<?> md = path.getMetadata(); if (path.getMetadata().getPathType() != PathType.PROPERTY) { md = md.getParent().getMetadata(); } MappedClass mc = configuration.getMappedClass(md.getParent().getType()); return mc.getMappedPath(md.getElement().toString()); } protected UID getTypeForDomainClass(Class<?> clazz) { MappedClass mc = configuration.getMappedClass(clazz); if (mc.getUID() != null) { return mc.getUID(); } else { throw new IllegalArgumentException("Got no RDF type for " + clazz.getName()); } } private Block handleRootPath(Path<?> path, Filters filters) { MappedClass mappedClass = configuration.getMappedClass(path.getType()); UID rdfType = mappedClass.getUID(); UID context = mappedClass.getContext(); QID pathNode = new QID(path.toString()); pathToMapped.put(path, pathNode); if (rdfType != null) { Collection<UID> types = ontology.getSubtypes(rdfType); if (types.size() > 1 && inferenceOptions.subClassOf()) { QID type = new QID(path + "_type"); filters.add(type.in(types)); return Blocks.pattern(pathNode, RDF.type, type); } else { if (context != null) { pathToContext.put(path, context); return Blocks.pattern(pathNode, RDF.type, rdfType, context); } else { return Blocks.pattern(pathNode, RDF.type, rdfType); } } } else { throw new IllegalArgumentException("No types mapped against " + path.getType().getName()); } } private boolean inNegation() { int notIndex = operatorStack.lastIndexOf(Ops.NOT); if (notIndex > -1) { int existsIndex = operatorStack.lastIndexOf(Ops.EXISTS); return notIndex > existsIndex; } return false; } private boolean inOptionalPath() { return operatorStack.contains(Ops.IS_NULL) || operatorStack.contains(Ops.MAP_IS_EMPTY) || operatorStack.contains(Ops.CASE_WHEN) || (operatorStack.contains(Ops.OR) && operatorStack.peek() != Ops.OR); } @Nullable private Expression<?> transform(Expression<?> expr, Filters filters) { return (Expression<?>) expr.accept(this, filters); } @SuppressWarnings("unchecked") private OrderSpecifier<?> transform(Filters filters, OrderSpecifier<?> os) { return new OrderSpecifier(os.getOrder(), transform(os.getTarget(), filters)); } @Nullable private Predicate transform(Predicate expr, Filters filters) { return (Predicate) expr.accept(this, filters); } @SuppressWarnings("unchecked") private Operation<?> transformPathEqNeConstant(Operation<?> operation) { Path<?> path = (Path<?>) operation.getArg(0); Constant<?> constant = (Constant<?>) operation.getArg(1); MappedPath mappedPath = getMappedPath(path); // id property if (path.getMetadata().getPathType() == PathType.PROPERTY && constant.getType().equals(String.class) && mappedPath.getPredicatePath().isEmpty()) { operation = PredicateOperation.create((Operator) operation.getOperator(), path, new ConstantImpl<ID>(session.getId(new LID(constant.toString())))); // localized property } else if (mappedPath.getMappedProperty().isLocalized()) { Locale locale; if (path.getMetadata().getPathType() == PathType.PROPERTY) { locale = session.getCurrentLocale(); } else { locale = (Locale) path.getMetadata().getElement(); } operation = PredicateOperation.create((Operator) operation.getOperator(), path, new ConstantImpl<LIT>(new LIT(constant.toString(), locale))); } return operation; } private Param<NODE> var(String var) { return new QNODE<NODE>(NODE.class, var); } public Expression<?> visit(Constant<?> constant, Filters filters) { Object javaValue = constant.getConstant(); ConverterRegistry converter = configuration.getConverterRegistry(); if (List.class.isAssignableFrom(constant.getType()) && ((List<?>) constant.getConstant()).isEmpty()) { return new ConstantImpl<UID>(RDF.nil); } else if (NODE.class.isAssignableFrom(constant.getType())) { return constant; } else if (javaValue instanceof Class<?>) { UID datatype = converter.getDatatype((Class<?>) javaValue); if (datatype != null) { return new ConstantImpl<UID>(datatype); } else { return new ConstantImpl<UID>(getTypeForDomainClass((Class<?>) javaValue)); } } else if (javaValue instanceof String) { return new ConstantImpl<LIT>(new LIT(javaValue.toString(), XSD.stringType)); } else if (converter.supports(javaValue.getClass())) { String label = converter.toString(javaValue); UID datatype = converter.getDatatype(javaValue.getClass()); return new ConstantImpl<LIT>(new LIT(label, datatype)); } else { ID id = session.getId(javaValue); return new ConstantImpl<ID>(id); } } @Override public Object visit(FactoryExpression<?> expr, Filters context) { throw new UnsupportedOperationException(); } @Nullable @SuppressWarnings("unchecked") public Expression<?> visit(Operation<?> expr, Filters filters) { boolean filtersInOptional = filters.inOptional(); boolean outerOptional = inOptionalPath(); boolean innerOptional = false; operatorStack.push(expr.getOperator()); Map<Path<?>, ParamExpression<?>> origPathToMapped = null; Map<Path<?>, ParamExpression<?>> origPathToKnown = null; if (!outerOptional && inOptionalPath()) { filters.beginOptional(); innerOptional = true; origPathToMapped = pathToMapped; origPathToKnown = pathToKnown; pathToMapped = new HashMap<Path<?>, ParamExpression<?>>(pathToMapped); pathToKnown = new HashMap<Path<?>, ParamExpression<?>>(pathToKnown); } List<Expression<?>> args = new ArrayList<Expression<?>>(expr.getArgs().size()); boolean leftPath = expr.getArg(0) instanceof Path<?>; boolean rightPath = expr.getArgs().size() > 1 ? expr.getArg(1) instanceof Path<?> : false; boolean leftConstant = expr.getArg(0) instanceof Constant<?>; boolean rightConstant = expr.getArgs().size() > 1 ? expr.getArg(1) instanceof Constant<?> : false; try { if (!queryOptions.isPreserveStringOps()) { if (expr.getOperator() == Ops.STARTS_WITH && rightConstant) { expr = PredicateOperation.create(Ops.MATCHES, expr.getArg(0), new ConstantImpl(new LIT("^" + expr.getArg(1)))); } else if (expr.getOperator() == Ops.STARTS_WITH_IC && rightConstant) { expr = PredicateOperation.create(Ops.MATCHES_IC, expr.getArg(0), new ConstantImpl(new LIT("^" + expr.getArg(1)))); } else if (expr.getOperator() == Ops.ENDS_WITH && rightConstant) { expr = PredicateOperation.create(Ops.MATCHES, expr.getArg(0), new ConstantImpl(new LIT(expr.getArg(1) + "$"))); } else if (expr.getOperator() == Ops.ENDS_WITH_IC && rightConstant) { expr = PredicateOperation.create(Ops.MATCHES_IC, expr.getArg(0), new ConstantImpl(new LIT(expr.getArg(1) + "$"))); } else if (expr.getOperator() == Ops.STRING_CONTAINS && rightConstant) { expr = PredicateOperation.create(Ops.MATCHES, expr.getArg(0), new ConstantImpl(new LIT(".*" + expr.getArg(1) + ".*"))); } else if (expr.getOperator() == Ops.STRING_CONTAINS_IC && rightConstant) { expr = PredicateOperation.create(Ops.MATCHES_IC, expr.getArg(0), new ConstantImpl(new LIT(".*" + expr.getArg(1) + ".*"))); } } if (expr.getOperator() == Ops.EQ || expr.getOperator() == Ops.NE) { if (leftPath && rightConstant && ((Path) expr.getArg(0)).getMetadata().getPathType() != PathType.VARIABLE) { expr = transformPathEqNeConstant(expr); } if (expr.getOperator() == Ops.EQ && expr.getArg(0) instanceof Path && !inOptionalPath() && !inNegation()) { if (expr.getArg(1) instanceof Constant) { ParamExpression<?> lhs = (ParamExpression<?>) transform(expr.getArg(0), filters); Constant<?> rhs = (Constant<?>) transform(expr.getArg(1), filters); params.put(lhs, rhs.getConstant()); return null; } else if (expr.getArg(1) instanceof Path) { if (pathToMapped.containsKey(expr.getArg(0))) { if (!pathToMapped.containsKey(expr.getArg(1))) { pathToKnown.put((Path<?>) expr.getArg(1), (ParamExpression<?>) transform(expr.getArg(0), filters)); transform(expr.getArg(1), filters); return null; } } else { pathToKnown.put((Path<?>) expr.getArg(0), (ParamExpression<?>) transform(expr.getArg(1), filters)); transform(expr.getArg(0), filters); return null; } } } } else if (expr.getOperator() == Ops.AND) { Predicate lhs, rhs; if (expr.getArg(0) instanceof Operation && ((Operation) expr.getArg(0)).getOperator() == Ops.OR) { lhs = (Predicate) transform(expr.getArg(1), filters); rhs = (Predicate) transform(expr.getArg(0), filters); } else { lhs = (Predicate) transform(expr.getArg(0), filters); rhs = (Predicate) transform(expr.getArg(1), filters); } return lhs == null ? rhs : (rhs == null ? lhs : ExpressionUtils.and(lhs, rhs)); } else if (expr.getOperator() == Ops.IN) { Expression<?> lhs = expr.getArg(0); if (leftPath) { lhs = transform(lhs, filters); } if ((leftPath && rightPath) || (leftConstant && rightPath)) { expr = (Operation) ExpressionUtils.eq(lhs, (Expression) expr.getArg(1)); } else if (leftPath && rightConstant) { Collection col = (Collection) ((Constant) expr.getArg(1)).getConstant(); if (!col.isEmpty()) { BooleanBuilder builder = new BooleanBuilder(); for (Object o : col) { builder.or(ExpressionUtils.eq(lhs, new ConstantImpl(o))); } expr = (Operation) builder.getValue(); } else { throw new IllegalArgumentException(expr.toString()); } } else { throw new IllegalArgumentException(expr.toString()); } } else if (expr.getOperator() == Ops.BETWEEN) { Operator<Boolean> first = Ops.GOE, second = Ops.LOE; expr = (Operation<?>) ExpressionUtils.and( PredicateOperation.create(first, expr.getArg(0), expr.getArg(1)), PredicateOperation.create(second, expr.getArg(0), expr.getArg(2))); } else if (expr.getOperator() == Ops.ORDINAL) { Param<?> path = (Param<?>) transform(expr.getArg(0), filters); Param<?> ordinalPath = new QLIT(path.getName() + "_ordinal"); filters.add(Blocks.pattern(path, CORE.enumOrdinal, ordinalPath)); return ordinalPath; } else if (expr.getOperator() == Ops.INSTANCE_OF) { Param<?> path = (Param<?>) transform(expr.getArg(0), filters); Constant<?> type = (Constant<?>) transform(expr.getArg(1), filters); Block pattern = Blocks.pattern(path, RDF.type, type); if (inNegation() || inOptionalPath()) { return pattern.exists(); } else { filters.add(pattern); return null; } } else if (expr.getOperator() == Ops.COL_IS_EMPTY) { expr = (Operation<?>) ExpressionUtils.eq(expr.getArg(0), new ConstantImpl(RDF.nil)); } else if (expr.getOperator() == Ops.CONTAINS_KEY) { Path<?> path = (Path<?>) expr.getArg(0); MappedPath mappedPath = getMappedPath(path); MappedProperty mappedProperty = mappedPath.getMappedProperty(); Expression<?> key = transform(expr.getArg(1), filters); Block pattern = Blocks.pattern(transform(path, filters), mappedProperty.getKeyPredicate(), key); if (inNegation() || inOptionalPath()) { return pattern.exists(); } else { filters.add(pattern); return null; } } else if (expr.getOperator() == Ops.CONTAINS_VALUE) { Path<?> path = (Path<?>) expr.getArg(0); MappedPath mappedPath = getMappedPath(path); MappedProperty mappedProperty = mappedPath.getMappedProperty(); if (mappedProperty.getValuePredicate() != null) { Expression<?> value = transform(expr.getArg(1), filters); Block pattern = Blocks.pattern(transform(path, filters), mappedProperty.getValuePredicate(), value); if (inNegation() || inOptionalPath()) { return pattern.exists(); } else { filters.add(pattern); return null; } } else { expr = (Operation<?>) ExpressionUtils.eq((Path) path, expr.getArg(1)); } } else if (expr.getOperator() == Ops.MAP_IS_EMPTY) { return PredicateOperation.create(Ops.IS_NULL, transform(expr.getArg(0), filters)); } else if (expr.getOperator() == Ops.COALESCE) { List<Expression<?>> elements = new ArrayList<Expression<?>>(); expr = new OperationImpl(expr.getType(), Ops.COALESCE, ImmutableList.copyOf(transformList(expr.getArg(0), elements))); } if (operatorStack.peek() != expr.getOperator()) { operatorStack.pop(); operatorStack.push(expr.getOperator()); } for (Expression<?> arg : expr.getArgs()) { Expression<?> transformed = transform(arg, filters); if (transformed != null) { args.add(transformed); } else { logger.error(arg + " skipped"); } } } finally { operatorStack.pop(); if (!outerOptional && innerOptional) { if (!filtersInOptional) { filters.endOptional(); } pathToMapped = origPathToMapped; pathToKnown = origPathToKnown; } } if (expr.getType().equals(Boolean.class)) { return new PredicateOperation((Operator) expr.getOperator(), ImmutableList.copyOf(args)); } else { return new OperationImpl(expr.getType(), expr.getOperator(), ImmutableList.copyOf(args)); } } private List<Expression<?>> transformList(Expression<?> expr, List<Expression<?>> elements) { if (expr instanceof Operation<?> && ((Operation<?>) expr).getOperator() == Ops.LIST) { Operation<?> list = (Operation<?>) expr; transformList(list.getArg(0), elements); elements.add(list.getArg(1)); } else { elements.add(expr); } return elements; } @Override public Object visit(ParamExpression<?> expr, Filters context) { if (NODE.class.isAssignableFrom(expr.getType())) { return expr; } else { throw new UnsupportedOperationException(); } } @SuppressWarnings("unchecked") @Nullable public Expression<?> visit(Path<?> path, Filters filters) { if (pathToMapped.containsKey(path)) { return pathToMapped.get(path); } else if (path.getMetadata().getParent() != null) { PathMetadata<?> md = path.getMetadata(); PathType pathType = md.getPathType(); ParamExpression<?> parent = (ParamExpression<?>) visit(md.getParent(), filters); ParamExpression<?> pathNode = null; ParamExpression<?> rdfPath = pathToKnown.get(path); UID context = getContext(path); if (pathType.equals(PathType.PROPERTY)) { MappedPath mappedPath = getMappedPath(path); List<MappedPredicate> predPath = mappedPath.getPredicatePath(); if (predPath.size() > 0) { MappedPredicate last = predPath.get(predPath.size() - 1); if (last.includeInferred()) { pathToContext.put(path, context = null); } else if (last.getContext() != null) { pathToContext.put(path, context = last.getContext()); } else if (configuration.isMapped(path.getType())) { MappedClass mappedClass = configuration.getMappedClass(path.getType()); if (mappedClass.getClass() != null) { pathToContext.put(path, mappedClass.getContext()); } } for (int i = 0; i < predPath.size(); i++) { MappedPredicate pred = predPath.get(i); UID c = pred.getContext() != null ? pred.getContext() : context; if (rdfPath != null && (i == predPath.size() - 1)) { pathNode = rdfPath; } else if (i == predPath.size() - 1) { String var = path.accept(ToStringVisitor.DEFAULT, TEMPLATES); pathNode = var(var); varNames.disallow(var); } else { pathNode = var(varNames.next()); } if (!pred.inv()) { filters.add(Blocks.pattern(parent, pred.getUID(), pathNode, c)); } else { filters.add(Blocks.pattern(pathNode, pred.getUID(), parent, c)); } parent = pathNode; } } else { pathNode = parent; } } else if (pathType.equals(PathType.COLLECTION_ANY)) { return visit(path.getMetadata().getParent(), filters); } else if (pathType.equals(PathType.ARRAYVALUE) || pathType.equals(PathType.LISTVALUE)) { throw new UnsupportedOperationException(pathType + " not supported!"); } else if (pathType.equals(PathType.ARRAYVALUE) || pathType.equals(PathType.LISTVALUE_CONSTANT)) { int index = ((Integer) md.getElement()).intValue(); for (int i = 0; i < index; i++) { pathNode = var(varNames.next()); filters.add(Blocks.pattern(parent, RDF.rest, pathNode, context)); parent = pathNode; } pathNode = var(varNames.next()); filters.add(Blocks.pattern(parent, RDF.first, pathNode, context)); } else if (pathType.equals(PathType.MAPVALUE) || pathType.equals(PathType.MAPVALUE_CONSTANT)) { MappedPath mappedPath = getMappedPath(md.getParent()); MappedProperty<?> mappedProperty = mappedPath.getMappedProperty(); if (!mappedProperty.isLocalized()) { Object element = path.getMetadata().getElement(); Expression<?> expr = transform(element instanceof Expression ? (Expression) element : new ConstantImpl(element), filters); filters.add(Blocks.pattern(parent, mappedProperty.getKeyPredicate(), expr, context)); if (mappedProperty.getValuePredicate() != null) { pathNode = var(varNames.next()); filters.add(Blocks.pattern(parent, mappedProperty.getValuePredicate(), pathNode, context)); } else { pathNode = parent; } } else { pathNode = parent; } } pathToMapped.put(path, pathNode); return pathNode; } else { throw new IllegalArgumentException("Undeclared path " + path); } } public Expression<?> visit(SubQueryExpression<?> expr, Filters filters) { QueryMetadata md = expr.getMetadata(); Filters f = new Filters(); // from for (JoinExpression join : md.getJoins()) { f.add(handleRootPath((Path<?>) join.getTarget(), f)); } // where if (md.getWhere() != null) { f.add(transform(md.getWhere(), f)); } // select if (!md.getProjection().isEmpty()) { QueryMetadata rv = new DefaultQueryMetadata().noValidate(); for (Expression<?> e : md.getProjection()) { rv.addProjection(transform(e, f)); } rv.addWhere(f.asBlock()); return new SubQueryExpressionImpl<Object>(Object.class, rv); } else { return f.asBlock(); } } @SuppressWarnings("unchecked") public Expression<?> visit(TemplateExpression<?> template, Filters filters) { ImmutableList.Builder<Object> builder = ImmutableList.builder(); for (Object arg : template.getArgs()) { if (arg instanceof Expression) { arg = transform((Expression) arg, filters); } if (arg != null) { builder.add(arg); } } if (template.getType().equals(Boolean.class)) { return new BooleanTemplate(template.getTemplate(), builder.build()); } else { return new TemplateExpressionImpl(template.getType(), template.getTemplate(), builder.build()); } } }