package com.mysema.rdfbean.model; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import javax.annotation.Nullable; import com.google.common.collect.ImmutableSet; import com.mysema.query.BooleanBuilder; import com.mysema.query.JoinExpression; import com.mysema.query.QueryFlag; import com.mysema.query.QueryFlag.Position; import com.mysema.query.QueryMetadata; import com.mysema.query.QueryModifiers; import com.mysema.query.support.SerializerBase; 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.Operator; import com.mysema.query.types.Ops; import com.mysema.query.types.OrderSpecifier; import com.mysema.query.types.ParamExpression; import com.mysema.query.types.Predicate; import com.mysema.query.types.SubQueryExpression; import com.mysema.query.types.TemplateExpression; import com.mysema.rdfbean.xsd.ConverterRegistryImpl; /** * @author tiwe * */ public class SPARQLVisitor extends SerializerBase<SPARQLVisitor> implements RDFVisitor<Void, Void> { private static final Set<Operator<?>> REGEX_OPS = ImmutableSet.<Operator<?>> of( Ops.MATCHES, Ops.MATCHES_IC, Ops.STARTS_WITH, Ops.STARTS_WITH_IC, Ops.ENDS_WITH, Ops.ENDS_WITH_IC, Ops.STRING_CONTAINS, Ops.STRING_CONTAINS_IC, Ops.STRING_IS_EMPTY); @Nullable private PatternBlock lastPattern; private final String prefix; private final Stack<Operator<?>> operators = new Stack<Operator<?>>(); private boolean inlineAll = false; private boolean inlineResources = false; private boolean likeAsMatches = false; private boolean inToOr = false; @Nullable private QueryMetadata metadata; public SPARQLVisitor() { this(SPARQLTemplates.DEFAULT, ""); } public SPARQLVisitor(SPARQLTemplates templates, String prefix) { super(templates); setNormalize(false); this.prefix = prefix; } // @Override protected void appendAsString(Expression<?> expr) { Object constant; if (expr instanceof Constant<?>) { constant = ((Constant<?>) expr).getConstant(); } else if (expr instanceof ParamExpression<?> && metadata != null) { if (metadata.getParams().containsKey(expr)) { constant = metadata.getParams().get(expr); } else { constant = ((ParamExpression<?>) expr).getName(); } } else { constant = expr.toString(); } if (constant instanceof NODE) { append(((NODE) constant).getValue()); } else { append(constant.toString()); } } @Nullable public Void visit(QueryMetadata md, QueryLanguage<?, ?> queryType) { metadata = md; QueryModifiers mod = md.getModifiers(); append(prefix); Set<QueryFlag> flags = metadata.getFlags(); // start serialize(Position.START, flags); // select if (queryType == QueryLanguage.TUPLE) { append("SELECT "); if (md.isDistinct()) { append("DISTINCT "); } if (!md.getProjection().isEmpty()) { for (Expression<?> expr : md.getProjection()) { if (expr instanceof TemplateExpression<?> || expr instanceof com.mysema.query.types.Operation<?>) { append("(").handle(expr).append(")"); } else { handle(expr); } append(" "); } } else { append("*"); } append("\n"); // ask } else if (queryType == QueryLanguage.BOOLEAN) { append("ASK "); // construct } else if (queryType == QueryLanguage.GRAPH) { if (md.getProjection().size() == 1 && md.getProjection().get(0) instanceof GroupBlock) { append("CONSTRUCT ").handle("", md.getProjection()).append("\n"); } else { append("CONSTRUCT { ").handle("", md.getProjection()).append("}\n"); } } lastPattern = null; // from for (JoinExpression je : md.getJoins()) { UID uid = (UID) ((Constant<?>) je.getTarget()).getConstant(); append("FROM <").append(uid.getId()).append(">\n"); } // where if (md.getWhere() != null) { if (queryType != QueryLanguage.BOOLEAN) { append("WHERE \n "); } if (md.getWhere() instanceof GroupBlock) { handle(md.getWhere()); } else { append("{ ").handle(md.getWhere()).append("}"); } append("\n"); } // order if (!md.getOrderBy().isEmpty()) { append("ORDER BY "); boolean first = true; for (OrderSpecifier<?> order : md.getOrderBy()) { if (!first) { append(" "); } if (order.isAscending()) { handle(order.getTarget()); } else { append("DESC(").handle(order.getTarget()).append(")"); } first = false; } append("\n"); } // group by if (!md.getGroupBy().isEmpty()) { append("GROUP BY ").handle(" ", md.getGroupBy()).append("\n"); } // having if (md.getHaving() != null) { append("HAVING (").handle(md.getHaving()).append(")\n"); } // limit if (mod.getLimit() != null) { append("LIMIT ").append(mod.getLimit().toString()).append("\n"); } // offset if (mod.getOffset() != null) { append("OFFSET ").append(mod.getOffset().toString()).append("\n"); } metadata = null; return null; } @SuppressWarnings("unchecked") @Override public Void visit(SubQueryExpression<?> expr, Void context) { for (Map.Entry<ParamExpression<?>, Object> entry : metadata.getParams().entrySet()) { expr.getMetadata().setParam((ParamExpression) entry.getKey(), entry.getValue()); } if (!operators.isEmpty() && operators.peek() == Ops.EXISTS) { handle(expr.getMetadata().getWhere()); } else { visit(expr.getMetadata(), QueryLanguage.TUPLE); } return null; } @Nullable public Void visit(UnionBlock expr, @Nullable Void context) { lastPattern = null; boolean first = true; for (Block block : expr.getBlocks()) { if (!first) { append("UNION "); } if (block instanceof PatternBlock) { append("{ ").handle(block).append("} "); } else { handle(block); } lastPattern = null; first = false; } return null; } @Nullable public Void visit(GroupBlock expr, @Nullable Void context) { lastPattern = null; append("{ "); visitBlocks(expr.getBlocks()); visitFilter(expr.getFilters()); append("} "); lastPattern = null; return null; } @Nullable public Void visit(GraphBlock expr, @Nullable Void context) { lastPattern = null; append("GRAPH ").handle(expr.getContext()).append(" { "); visitBlocks(expr.getBlocks()); visitFilter(expr.getFilters()); append("} "); lastPattern = null; return null; } @Nullable public Void visit(OptionalBlock expr, @Nullable Void context) { lastPattern = null; append("OPTIONAL { "); visitBlocks(expr.getBlocks()); visitFilter(expr.getFilters()); append("} "); lastPattern = null; return null; } private void visitBlocks(List<Block> blocks) { for (Block block : blocks) { if (lastPattern != null && !(block instanceof PatternBlock)) { append(".\n "); lastPattern = null; } handle(block); } } private void visitFilter(@Nullable Predicate filter) { if (filter != null) { if (lastPattern != null) { append(". "); } lastPattern = null; append("FILTER(").handle(filter).append(") "); } lastPattern = null; } @SuppressWarnings("unchecked") @Override public void visitConstant(Object constant) { // convert literal values to LIT objects if (constant instanceof String) { constant = new LIT(constant.toString()); } else if (ConverterRegistryImpl.DEFAULT.supports(constant.getClass())) { UID datatype = ConverterRegistryImpl.DEFAULT.getDatatype(constant.getClass()); String value = ConverterRegistryImpl.DEFAULT.toString(constant); constant = new ConstantImpl<LIT>(LIT.class, new LIT(value, datatype)); } if (constant instanceof QueryMetadata) { QueryMetadata md = (QueryMetadata) constant; handle(md.getWhere()); } else if (constant instanceof Block) { handle((Expression<?>) constant); } else if (inlineAll && NODE.class.isInstance(constant)) { NODE node = (NODE) constant; if (node.isBNode()) { append("_:" + node.getValue()); } else if (node.isURI()) { append("<" + node.getValue() + ">"); } else { append(node.toString()); } } else if (inlineResources && UID.class.isInstance(constant)) { UID node = (UID) constant; append("<" + node.getValue() + ">"); } else if (Collection.class.isAssignableFrom(constant.getClass())) { boolean first = true; append("("); for (Object o : (Collection) constant) { if (!first) { append(", "); } visitConstant(o); first = false; } append(")"); } else if (!getConstantToLabel().containsKey(constant)) { String constLabel = "_c" + (getConstantToLabel().size() + 1); getConstantToLabel().put(constant, constLabel); append("?" + constLabel); } else { append("?" + getConstantToLabel().get(constant)); } } @Nullable public Void visit(PatternBlock expr, @Nullable Void context) { // resource = true; if (lastPattern == null || !lastPattern.getSubject().equals(expr.getSubject())) { if (lastPattern != null) { append(".\n "); } handle(expr.getSubject()).append(" "); handle(expr.getPredicate()).append(" "); } else if (!lastPattern.getPredicate().equals(expr.getPredicate())) { append("; "); handle(expr.getPredicate()).append(" "); } else { append(", "); } handle(expr.getObject()).append(" "); lastPattern = expr; // resource = false; return null; } @SuppressWarnings("unchecked") @Override protected void visitOperation(Class<?> type, Operator<?> operator, List<? extends Expression<?>> args) { if (operator == Ops.IN && inToOr) { BooleanBuilder builder = new BooleanBuilder(); for (Object arg : ((Constant<Collection>)args.get(1)).getConstant()) { builder.or(ExpressionUtils.eq(args.get(0), new ConstantImpl(arg))); } builder.getValue().accept(this, null); return; } else if (operator == Ops.LIKE && likeAsMatches && args.get(1) instanceof Constant) { operator = Ops.MATCHES; String value = ((Constant<LIT>) args.get(1)).getConstant().getValue().replace("%", ".*").replace("_", "."); args = Arrays.asList(args.get(0), new ConstantImpl<LIT>(LIT.class, new LIT(value))); } else if (REGEX_OPS.contains(operator) && args.get(1) instanceof Constant && ((Constant) args.get(1)).getConstant() instanceof LIT) { args = Arrays.<Expression<?>> asList(args.get(0), new ConstantImpl(((Constant<LIT>) args.get(1)).getConstant().getValue())); } operators.push(operator); try { if (operator == Ops.NUMCAST) { UID datatype = (UID) ((Constant<?>) args.get(1)).getConstant(); append("xsd:" + datatype.ln() + "("); handle(args.get(0)); append(")"); } else { super.visitOperation(type, operator, args); } } finally { operators.pop(); } } @Override @Nullable public Void visit(ParamExpression<?> param, @Nullable Void context) { getConstantToLabel().put(param, param.getName()); append("?" + param.getName()); return null; } public void addBindings(SPARQLQuery query, QueryMetadata md) { for (Map.Entry<Object, String> entry : getConstantToLabel().entrySet()) { if (entry.getKey() instanceof ParamExpression<?>) { if (md.getParams().containsKey(entry.getKey())) { query.setBinding(entry.getValue(), (NODE) md.getParams().get(entry.getKey())); } } else { query.setBinding(entry.getValue(), (NODE) entry.getKey()); } } } public void setInlineResources(boolean b) { inlineResources = b; } public void setLikeAsMatches(boolean likeAsMatches) { this.likeAsMatches = likeAsMatches; } public void setInlineAll(boolean inlineAll) { this.inlineAll = inlineAll; } public void setInToOr(boolean inToOr) { this.inToOr = inToOr; } }