/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jena.arq.querybuilder.handlers; import java.util.Collections; import java.util.List; import java.util.Map; import org.apache.jena.arq.querybuilder.AbstractQueryBuilder; import org.apache.jena.arq.querybuilder.SelectBuilder; import org.apache.jena.arq.querybuilder.rewriters.ElementRewriter; import org.apache.jena.graph.Node; import org.apache.jena.graph.NodeFactory; import org.apache.jena.graph.Triple; import org.apache.jena.query.Query; import org.apache.jena.sparql.core.Var; import org.apache.jena.sparql.expr.Expr; import org.apache.jena.sparql.lang.sparql_11.ParseException; import org.apache.jena.sparql.syntax.*; import org.apache.jena.sparql.util.ExprUtils; import org.apache.jena.vocabulary.RDF; /** * The where handler. Generally handles GroupGraphPattern. * * @see <a href= * "http://www.w3.org/TR/2013/REC-sparql11-query-20130321/#rGroupGraphPattern"> * SPARQL 11 Query Language - Group Graph Pattern</a> * */ public class WhereHandler implements Handler { // the query to modify private final Query query; /** * Constructor. * * @param query * The query to manipulate. */ public WhereHandler(Query query) { this.query = query; } /** * Add all where attributes from the Where Handler argument. * * @param whereHandler * The Where Handler to copy from. */ public void addAll(WhereHandler whereHandler) { Element e = whereHandler.query.getQueryPattern(); if (e != null) { // clone the Element ElementRewriter rewriter = new ElementRewriter(Collections.emptyMap()); e.visit(rewriter); Element clone = rewriter.getResult(); Element mine = query.getQueryPattern(); if (mine == null) { query.setQueryPattern(clone); } else { ElementGroup eg = null; if (mine instanceof ElementGroup) { eg = (ElementGroup) mine; } else { eg = new ElementGroup(); eg.addElement(mine); } if (clone instanceof ElementGroup) { for (Element ele : ((ElementGroup) clone).getElements()) { eg.addElement(ele); } } else { eg.addElement(clone); } query.setQueryPattern(eg); } } } /** * Get the base element from the where clause. If the clause does not * contain an element return the element group, otherwise return the * enclosed elelment. * * @return the base element. */ private Element getElement() { Element result = query.getQueryPattern(); if (result == null) { result = getClause(); } return result; } /** * Get the element group for the clause. if The element group is not set, * create and set it. * * @return The element group. */ private ElementGroup getClause() { Element e = query.getQueryPattern(); if (e == null) { e = new ElementGroup(); query.setQueryPattern(e); } if (e instanceof ElementGroup) { return (ElementGroup) e; } ElementGroup eg = new ElementGroup(); eg.addElement(e); ; query.setQueryPattern(eg); return eg; } /** * Test that a triple is valid. Throws an IllegalArgumentException if the * triple is not valid. * * @param t * The trip to test. */ private void testTriple(Triple t) { // verify Triple is valid boolean validSubject = t.getSubject().isURI() || t.getSubject().isBlank() || t.getSubject().isVariable() || t.getSubject().equals(Node.ANY); boolean validPredicate = t.getPredicate().isURI() || t.getPredicate().isVariable() || t.getPredicate().equals(Node.ANY); boolean validObject = t.getObject().isURI() || t.getObject().isLiteral() || t.getObject().isBlank() || t.getObject().isVariable() || t.getObject().equals(Node.ANY); if (!validSubject || !validPredicate || !validObject) { StringBuilder sb = new StringBuilder(); if (!validSubject) { sb.append(String.format("Subject (%s) must be a URI, blank, variable, or a wildcard. %n", t.getSubject())); } if (!validPredicate) { sb.append( String.format("Predicate (%s) must be a URI , variable, or a wildcard. %n", t.getPredicate())); } if (!validObject) { sb.append(String.format("Object (%s) must be a URI, literal, blank, , variable, or a wildcard. %n", t.getObject())); } if (!validSubject || !validPredicate) { sb.append(String.format("Is a prefix missing? Prefix must be defined before use. %n")); } throw new IllegalArgumentException(sb.toString()); } } /** * Add the triple to the where clause * * @param t * The triple to add. * @throws IllegalArgumentException * If the triple is not a valid triple for a where clause. */ public void addWhere(Triple t) throws IllegalArgumentException { testTriple(t); ElementGroup eg = getClause(); List<Element> lst = eg.getElements(); if (lst.isEmpty()) { ElementPathBlock epb = new ElementPathBlock(); epb.addTriple(t); eg.addElement(epb); } else { Element e = lst.get(lst.size() - 1); if (e instanceof ElementTriplesBlock) { ElementTriplesBlock etb = (ElementTriplesBlock) e; etb.addTriple(t); } else if (e instanceof ElementPathBlock) { ElementPathBlock epb = (ElementPathBlock) e; epb.addTriple(t); } else { ElementPathBlock etb = new ElementPathBlock(); etb.addTriple(t); eg.addElement(etb); } } } /** * Add an optional triple to the where clause * * @param t * The triple to add. * @throws IllegalArgumentException * If the triple is not a valid triple for a where clause. */ public void addOptional(Triple t) throws IllegalArgumentException { testTriple(t); ElementPathBlock epb = new ElementPathBlock(); epb.addTriple(t); ElementOptional opt = new ElementOptional(epb); getClause().addElement(opt); } /** * Add the contents of a where handler as an optional statement. * @param whereHandler The where handler to use as the optional statement. */ public void addOptional(WhereHandler whereHandler) { getClause().addElement(new ElementOptional(whereHandler.getClause())); } /** * Add an expression string as a filter. * * @param expression * The expression string to add. * @throws ParseException * If the expression can not be parsed. */ public void addFilter(String expression) throws ParseException { getClause().addElement(new ElementFilter(ExprUtils.parse(query, expression, true))); } /** * add an expression as a filter. * * @param expr * The expression to add. */ public void addFilter(Expr expr) { getClause().addElement(new ElementFilter(expr)); } /** * Add a subquery to the where clause. * * @param subQuery * The sub query to add. */ public void addSubQuery(SelectBuilder subQuery) { getClause().addElement(makeSubQuery(subQuery)); } /** * Convert a subquery into a subquery element. * * @param subQuery * The sub query to convert * @return THe converted element. */ private ElementSubQuery makeSubQuery(AbstractQueryBuilder<?> subQuery) { Query q = new Query(); SelectHandler sh = subQuery.getHandlerBlock().getSelectHandler(); if (sh != null) { if (! sh.getProject().isEmpty()) { q.setQuerySelectType(); } } PrologHandler ph = new PrologHandler(query); ph.addPrefixes( subQuery.getPrologHandler().getPrefixes() ); HandlerBlock handlerBlock = new HandlerBlock(q); handlerBlock.addAll( subQuery.getHandlerBlock() ); // remove the prefix mappings from the sub query. handlerBlock.getPrologHandler().clearPrefixes(); // make sure we have a query pattern before we start building. if (q.getQueryPattern() == null) { q.setQueryPattern( new ElementGroup() ); } handlerBlock.build(); return new ElementSubQuery(q); } /** * Add a union to the where clause. * * @param subQuery * The subquery to add as the union. */ public void addUnion(SelectBuilder subQuery) { ElementUnion union = null; ElementGroup clause = getClause(); // if the last element is a union make sure we add to it. if (!clause.isEmpty()) { Element lastElement = clause.getElements().get(clause.getElements().size() - 1); if (lastElement instanceof ElementUnion) { union = (ElementUnion) lastElement; } else { // clauses is not empty and is not a union so it is the left // side of the union. union = new ElementUnion(); union.addElement(clause); query.setQueryPattern(union); } } else { // add the union as the first element in the clause. union = new ElementUnion(); clause.addElement(union); } // if there are projected vars then do a full blown subquery // otherwise just add the clause. if (subQuery.getVars().size() > 0) { union.addElement(makeSubQuery(subQuery)); } else { PrologHandler ph = new PrologHandler(query); ph.addPrefixes(subQuery.getPrologHandler().getPrefixes()); union.addElement(subQuery.getWhereHandler().getClause()); } } /** * Add a graph to the where clause. * * @param graph * The name of the graph. * @param subQuery * The where handler that defines the graph. */ public void addGraph(Node graph, WhereHandler subQuery) { getClause().addElement(new ElementNamedGraph(graph, subQuery.getElement())); } /** * Add a binding to the where clause. * * @param expr * The expression to bind. * @param var * The variable to bind it to. */ public void addBind(Expr expr, Var var) { getClause().addElement(new ElementBind(var, expr)); } /** * Add a binding to the where clause. * * @param expression * The expression to bind. * @param var * The variable to bind it to. * @throws ParseException */ public void addBind(String expression, Var var) throws ParseException { getClause().addElement(new ElementBind(var, ExprUtils.parse(query, expression, true))); } @Override public void setVars(Map<Var, Node> values) { if (values.isEmpty()) { return; } Element e = query.getQueryPattern(); if (e != null) { ElementRewriter r = new ElementRewriter(values); e.visit(r); query.setQueryPattern(r.getResult()); } } @Override public void build() { // no special operations required. } /** * Create a list node from a list of objects as per RDF Collections. * * http://www.w3.org/TR/2013/REC-sparql11-query-20130321/#collections * * @param objs * the list of objects for the list. * @return the first blank node in the list. */ public Node list(Object... objs) { Node retval = NodeFactory.createBlankNode(); Node lastObject = retval; for (int i = 0; i < objs.length; i++) { Node n = AbstractQueryBuilder.makeNode(objs[i], query.getPrefixMapping()); addWhere(new Triple(lastObject, RDF.first.asNode(), n)); if (i + 1 < objs.length) { Node nextObject = NodeFactory.createBlankNode(); addWhere(new Triple(lastObject, RDF.rest.asNode(), nextObject)); lastObject = nextObject; } else { addWhere(new Triple(lastObject, RDF.rest.asNode(), RDF.nil.asNode())); } } return retval; } }