/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Mar 3, 2012
*/
package com.bigdata.rdf.sparql.ast.service;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.openrdf.model.BNode;
import org.openrdf.model.Value;
import org.openrdf.query.Binding;
import org.openrdf.query.BindingSet;
import com.bigdata.bop.IVariable;
/**
* Utility class constructs a valid SPARQL query for a remote
* <code>SPARQL 1.1</code> using the <code>BINDINGS</code> clause to vector
* solutions into that remote end point.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id: RemoteSparqlQueryBuilder.java 6071 2012-03-04 18:08:57Z
* thompsonbry $
*/
public class RemoteSparql11DraftQueryBuilder extends RemoteSparql11QueryBuilder {
private static final Logger log = Logger
.getLogger(RemoteSparql10QueryBuilder.class);
/**
*
* @param serviceNode
* The SERVICE clause.
*/
public RemoteSparql11DraftQueryBuilder(final ServiceNode serviceNode) {
super(serviceNode);
}
public String getSparqlQuery(final BindingSet[] bindingSets) {
final StringBuilder sb = new StringBuilder();
/*
* Prefix declarations.
*
* Note: The prefix declarations need to be harvested and passed along
* since we are using the text image of the SERVICE group graph pattern
* in the WHERE clause.
*/
if (prefixDecls != null) {
for (Map.Entry<String, String> e : prefixDecls.entrySet()) {
sb.append("\n");
sb.append("prefix ");
sb.append(e.getKey());
sb.append(":");
sb.append(" <");
sb.append(e.getValue());
sb.append(">");
sb.append("\n");
}
}
/*
* SELECT clause.
*/
{
sb.append("SELECT ");
if (projectedVars.isEmpty()) {
/*
* Note: This is a dubious hack for openrdf federated query
* testEmptyServiceBlock. Since there are no variables in the
* service clause, it was sending an invalid SELECT expression.
* It is now hacked to send a "*" instead.
*/
sb.append("*");
} else {
for (IVariable<?> v : projectedVars) {
sb.append(" ?");
sb.append(v.getName());
}
}
sb.append("\n");
}
/*
* When true, there is only one binding set to be vectored and it is
* empty. We DO NOT use the BINDINGS clause for this case in order to be
* compatible with services which do and do not support BINDINGS.
*/
final boolean singleEmptyBindingSet = (bindingSets.length == 0)
|| (bindingSets.length == 1 && bindingSets[0].size() == 0);
/*
* Correlated blank node / variables map.
*
* Note: This map is used outside of the loop over the binding sets
* because we can only handle correlated variables via the BINDINGS
* clause when there is a single solution being flowed into the
* remote service.
*/
Map<BNode,Set<String/*vars*/>> bnodes = null;
if (!singleEmptyBindingSet) {
bnodes = getCorrelatedVariables(bindingSets);
}
/*
* WHERE clause.
*
* Note: This uses the actual SPARQL text image for the SERVICE's graph
* pattern.
*
* Note: If there are variables which are correlated through shared
* variables (and there is only one solution to be vectored) then we
* will impose a same-term constraint on those variables as part of the
* generated query.
*/
{
/*
* Clip out the graph pattern from the SERVICE clause.
*
* Note: This leaves off the outer curly brackets in case we need to
* add some FILTERS, BINDS, etc. into the WHERE clause.
*/
final int beginIndex = exprImage.indexOf("{") + 1 ;
if (beginIndex < 0)
throw new RuntimeException();
final int endIndex = exprImage.lastIndexOf("}");
if (endIndex < beginIndex)
throw new RuntimeException();
final String tmp = exprImage.substring(beginIndex, endIndex);
sb.append("WHERE {\n");
if (bnodes != null) {
/*
* Impose a same-term constraint for all variables which are
* bound to the same blank node.
*/
for(Set<String> sameTermVars : bnodes.values()) {
final int nSameTerm = sameTermVars.size();
if (nSameTerm < 2)
continue;
final String[] names = sameTermVars
.toArray(new String[nSameTerm]);
sb.append("FILTER (");
for (int i = 1; i < names.length; i++) {
if (i > 1) {
sb.append(" &&");
}
sb.append(" sameTerm( ?" + names[0] + ", ?" + names[i]
+ ")");
}
sb.append(" ).\n");
}
}
sb.append(tmp); // append SERVICE's graph pattern.
sb.append("\n}\n");
}
/*
* BINDINGS clause.
*
* Note: The BINDINGS clause is used to vector the SERVICE request.
*
* BINDINGS ?book ?title { (:book1 :title1) (:book2 UNDEF) }
*/
if (!singleEmptyBindingSet) {
// Variables in a known stable order.
final LinkedHashSet<String> vars = getDistinctVars(bindingSets);
sb.append("BINDINGS");
// Variable declarations.
{
for (String v : vars) {
sb.append(" ?");
sb.append(v);
}
}
// Bindings.
{
sb.append(" {\n"); //
for (BindingSet bindingSet : bindingSets) {
sb.append("(");
for (String v : vars) {
sb.append(" ");
final Binding b = bindingSet.getBinding(v);
if (b == null) {
sb.append("UNDEF");
} else {
final Value val = b.getValue();
final String ext = util.toExternal(val);
sb.append(ext);
}
}
sb.append(" )");
sb.append("\n");
}
sb.append("}\n");
}
}
final String q = sb.toString();
if (log.isInfoEnabled())
log.info("\n" + q);
return q;
}
}