/* * Copyright (c) 2011 Talis Inc., Some rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of the openrdf.org nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ package org.openrdf.repository.object.advisers.helpers; import static org.openrdf.query.QueryLanguage.SPARQL; import info.aduna.xml.XMLWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.CharArrayWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.StringReader; import java.io.Writer; import java.net.MalformedURLException; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.transform.TransformerException; import org.openrdf.OpenRDFException; import org.openrdf.model.BNode; import org.openrdf.model.Literal; import org.openrdf.model.Model; import org.openrdf.model.Resource; import org.openrdf.model.Statement; import org.openrdf.model.URI; import org.openrdf.model.Value; import org.openrdf.model.impl.LinkedHashModel; import org.openrdf.query.BindingSet; import org.openrdf.query.BooleanQuery; import org.openrdf.query.GraphQuery; import org.openrdf.query.GraphQueryResult; import org.openrdf.query.MalformedQueryException; import org.openrdf.query.Operation; import org.openrdf.query.QueryEvaluationException; import org.openrdf.query.QueryResultUtil; import org.openrdf.query.TupleQuery; import org.openrdf.query.TupleQueryResult; import org.openrdf.query.TupleQueryResultHandlerException; import org.openrdf.query.Update; import org.openrdf.query.resultio.sparqlxml.SPARQLBooleanXMLWriter; import org.openrdf.query.resultio.sparqlxml.SPARQLResultsXMLWriter; import org.openrdf.repository.RepositoryException; import org.openrdf.repository.object.ObjectConnection; import org.openrdf.repository.object.ObjectQuery; import org.openrdf.repository.object.advisers.SparqlQuery; import org.openrdf.result.MultipleResultException; import org.openrdf.result.Result; import org.openrdf.rio.helpers.StatementCollector; import org.openrdf.rio.rdfxml.RDFXMLWriter; import org.w3c.dom.Document; import org.w3c.dom.DocumentFragment; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; public class SparqlEvaluator { private static final Pattern ILLEGAL_VAR = Pattern.compile("\\s|\\?"); private static final XMLInputFactory inFactory; private static final DocumentBuilderFactory documentBuilderFactory; static { XMLInputFactory factory = XMLInputFactory.newInstance(); factory.setProperty(XMLInputFactory.IS_VALIDATING, false); factory.setProperty(XMLInputFactory.SUPPORT_DTD, false); inFactory = factory; } static { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(false); factory.setNamespaceAware(true); factory.setIgnoringComments(false); factory.setIgnoringElementContentWhitespace(false); documentBuilderFactory = factory; } public class SparqlBuilder { private ObjectConnection con; private SparqlQuery query; private Map<String, Value> bindings = new HashMap<String, Value>(); private List<String> bindingNames = new ArrayList<String>(); private List<List<Value>> bindingValues = new ArrayList<List<Value>>(); private org.openrdf.repository.object.ObjectFactory of; public SparqlBuilder(ObjectConnection con, SparqlQuery query) { assert con != null; assert query != null; this.con = con; this.query = query; of = con.getObjectFactory(); } @Override public String toString() { return bindMultiples(query.toString()); } public SparqlBuilder with(String name, Set values) { boolean illegal = ILLEGAL_VAR.matcher(name).find(); if (illegal && values != null && !values.isEmpty()) { throw new IllegalArgumentException( "Invalide SPARQL variable name: '" + name + "'"); } List<List<Value>> list = new ArrayList<List<Value>>(); for (Object value : values) { for (List<Value> bindings : bindingValues) { List<Value> set = new ArrayList<Value>(bindings.size() + 1); set.addAll(bindings); if (value == null) { set.add(null); } else if (value instanceof Value) { set.add((Value) value); } else { set.add(of.createValue(value)); } list.add(set); } if (bindingValues.isEmpty()) { List<Value> set = new ArrayList<Value>(1); if (value == null) { set.add(null); } else if (value instanceof Value) { set.add((Value) value); } else { set.add(of.createValue(value)); } list.add(set); } } bindingValues = list; bindingNames.add(name); return this; } public SparqlBuilder with(String name, Object value) { if (value == null) { bindings.remove(name); } else if (value instanceof Value) { bindings.put(name, (Value) value); } else { bindings.put(name, of.createValue(value)); } return this; } public SparqlBuilder with(String name, boolean value) { bindings.put(name, con.getValueFactory().createLiteral(value)); return this; } public SparqlBuilder with(String name, char value) { bindings.put(name, con.getValueFactory().createLiteral(value)); return this; } public SparqlBuilder with(String name, byte value) { bindings.put(name, con.getValueFactory().createLiteral(value)); return this; } public SparqlBuilder with(String name, short value) { bindings.put(name, con.getValueFactory().createLiteral(value)); return this; } public SparqlBuilder with(String name, int value) { bindings.put(name, con.getValueFactory().createLiteral(value)); return this; } public SparqlBuilder with(String name, long value) { bindings.put(name, con.getValueFactory().createLiteral(value)); return this; } public SparqlBuilder with(String name, float value) { bindings.put(name, con.getValueFactory().createLiteral(value)); return this; } public SparqlBuilder with(String name, double value) { bindings.put(name, con.getValueFactory().createLiteral(value)); return this; } public Model asModel() throws OpenRDFException { GraphQuery qry = prepareGraphQuery(); Model model = new LinkedHashModel(); qry.evaluate(new StatementCollector(model)); return model; } public Statement asStatement() throws OpenRDFException { GraphQueryResult result = asGraphQueryResult(); try { if (result.hasNext()) { Statement stmt = result.next(); if (result.hasNext()) throw new MultipleResultException(); return stmt; } return null; } finally { result.close(); } } public URI asURI() throws OpenRDFException { return (URI) asResource(); } public BNode asBNode() throws OpenRDFException { return (BNode) asResource(); } public Resource asResource() throws OpenRDFException { return (Resource) asValue(); } public Literal asLiteral() throws OpenRDFException { return (Literal) asValue(); } public Value asValue() throws OpenRDFException { BindingSet bs = asBindingSet(); if (bs == null) return null; return bs.getValue(bs.getBindingNames().iterator().next()); } public BindingSet asBindingSet() throws OpenRDFException { TupleQueryResult result = asTupleQueryResult(); try { if (result.hasNext()) { BindingSet bindings = result.next(); if (result.hasNext()) throw new MultipleResultException(); return bindings; } return null; } finally { result.close(); } } public TupleQueryResult asTupleQueryResult() throws OpenRDFException { return prepareTupleQuery().evaluate(); } public GraphQueryResult asGraphQueryResult() throws OpenRDFException { return prepareGraphQuery().evaluate(); } public boolean asBoolean() throws OpenRDFException { if (query.isBooleanQuery()) return prepareBooleanQuery().evaluate(); return asResult(Boolean.class).singleResult().booleanValue(); } public char asChar() throws OpenRDFException { return asResult(Character.class).singleResult().charValue(); } public byte asByte() throws OpenRDFException { return asResult(Byte.class).singleResult().byteValue(); } public short asShort() throws OpenRDFException { return asResult(Short.class).singleResult().shortValue(); } public int asInt() throws OpenRDFException { return asResult(Integer.class).singleResult().intValue(); } public long asLong() throws OpenRDFException { return asResult(Long.class).singleResult().longValue(); } public float asFloat() throws OpenRDFException { return asResult(Float.class).singleResult().floatValue(); } public double asDouble() throws OpenRDFException { return asResult(Double.class).singleResult().doubleValue(); } public String asString() throws OpenRDFException { Result<String> result = asResult(String.class); if (result.hasNext()) return result.singleResult(); return null; } public CharSequence asCharSequence() throws OpenRDFException { Result<CharSequence> result = asResult(CharSequence.class); if (result.hasNext()) return result.singleResult(); return null; } public byte[] asByteArray() throws OpenRDFException { Result<byte[]> result = asResult(byte[].class); if (result.hasNext()) return result.singleResult(); return null; } public Set<? extends Value> asSetOfValues() throws OpenRDFException { Set<Value> set = new LinkedHashSet<Value>(); TupleQueryResult result = asTupleQueryResult(); try { if (result.getBindingNames().isEmpty()) return null; String name = result.getBindingNames().iterator().next(); while (result.hasNext()) { set.add(result.next().getValue(name)); } return set; } finally { result.close(); } } public Set asSet() throws OpenRDFException { return asResult().asSet(); } public Result asResult() throws OpenRDFException { ObjectQuery qry = prepareObjectQuery(Object.class); return qry.evaluate(); } public <T> Result<T> asResult(Class<T> of) throws OpenRDFException { if (of == null || Object.class.equals(of)) return asResult(); ObjectQuery qry = prepareObjectQuery(of); return qry.evaluate(of); } public <T> Set<T> asSet(Class<T> of) throws OpenRDFException { if (of == null || Object.class.equals(of)) return asSet(); if (BindingSet.class.equals(of)) { TupleQueryResult result = asTupleQueryResult(); try { List<BindingSet> list = new ArrayList<BindingSet>(); while (result.hasNext()) { list.add(result.next()); } return (Set<T>) list; } finally { result.close(); } } if (Value.class.isAssignableFrom(of)) return (Set<T>) asSetOfValues(); if (Statement.class.equals(of)) return (Set<T>) asModel(); return asResult(of).asSet(); } public <T> List<T> asList(Class<T> of) throws OpenRDFException { if (of == null || Object.class.equals(of)) return asList(); return asResult(of).asList(); } public <T> T as(Class<T> of) throws OpenRDFException { Result<T> result = asResult(of); if (result.hasNext()) return result.singleResult(); return null; } public List asList() throws OpenRDFException { return asResult().asList(); } public Document asDocument() throws OpenRDFException, TransformerException, IOException, ParserConfigurationException { DocumentBuilder builder = documentBuilderFactory .newDocumentBuilder(); InputStream in = asInputStream(); try { try { if (systemId == null) return builder.parse(in); return builder.parse(in, systemId); } catch (SAXException e) { throw new TransformerException(e); } finally { in.close(); } } catch (IOException e) { throw new TransformerException(e); } } public DocumentFragment asDocumentFragment() throws OpenRDFException, TransformerException, IOException, ParserConfigurationException { Document doc = asDocument(); DocumentFragment frag = doc.createDocumentFragment(); frag.appendChild(doc.getDocumentElement()); return frag; } public Element asElement() throws OpenRDFException, TransformerException, IOException, ParserConfigurationException { return asDocument().getDocumentElement(); } public Node asNode() throws OpenRDFException, TransformerException, IOException, ParserConfigurationException { return asDocument(); } public XMLEventReader asXMLEventReader() throws OpenRDFException, TransformerException, IOException, ParserConfigurationException, XMLStreamException { InputStream in = asInputStream(); try { if (systemId == null) return inFactory.createXMLEventReader(in); return inFactory.createXMLEventReader(systemId, in); } catch (XMLStreamException e) { throw new TransformerException(e); } } public ReadableByteChannel asReadableByteChannel() throws OpenRDFException, TransformerException, IOException, ParserConfigurationException, XMLStreamException { return Channels.newChannel(asInputStream()); } public ByteArrayOutputStream asByteArrayOutputStream() throws OpenRDFException, TransformerException, IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(8192); try { try { toOutputStream(output); } catch (TupleQueryResultHandlerException e) { throw new TransformerException(e); } catch (QueryEvaluationException e) { throw new TransformerException(e); } finally { output.close(); } } catch (IOException e) { throw new TransformerException(e); } return output; } public InputStream asInputStream() throws OpenRDFException, TransformerException, IOException { return new ByteArrayInputStream(asByteArrayOutputStream() .toByteArray()); } public Reader asReader() throws OpenRDFException, TransformerException, IOException { return new StringReader(asCharArrayWriter().toString()); } public Readable asReadable() throws TransformerException, OpenRDFException, IOException { return asReader(); } public CharArrayWriter asCharArrayWriter() throws OpenRDFException, TransformerException, IOException { CharArrayWriter writer = new CharArrayWriter(8192); toWriter(writer); return writer; } public void asUpdate() throws OpenRDFException { String base = query.getBaseURI(); String sparql = bindMultiples(query.toString()); Update qry = bindSingles(con.prepareUpdate(SPARQL, sparql, base)); qry.execute(); } public void toOutputStream(OutputStream output) throws OpenRDFException, TransformerException, IOException { if (query.isGraphQuery()) { QueryResultUtil.report(asGraphQueryResult(), new RDFXMLWriter( output)); } else if (query.isTupleQuery()) { QueryResultUtil.report(asTupleQueryResult(), new SPARQLResultsXMLWriter(output)); } else if (query.isBooleanQuery()) { new SPARQLBooleanXMLWriter(output).write(asBoolean()); } else { throw new AssertionError("Unknown query type"); } } public void toWriter(Writer writer) throws OpenRDFException, TransformerException, IOException { if (query.isGraphQuery()) { QueryResultUtil.report(asGraphQueryResult(), new RDFXMLWriter( writer)); } else if (query.isTupleQuery()) { QueryResultUtil.report(asTupleQueryResult(), new SPARQLResultsXMLWriter(new XMLWriter(writer))); } else if (query.isBooleanQuery()) { new SPARQLBooleanXMLWriter(new XMLWriter(writer)) .write(asBoolean()); } else { throw new AssertionError("Unknown query type"); } } private GraphQuery prepareGraphQuery() throws MalformedQueryException, RepositoryException { String sparql = bindMultiples(query.toString()); String base = query.getBaseURI(); return bindSingles(con.prepareGraphQuery(SPARQL, sparql, base)); } private TupleQuery prepareTupleQuery() throws MalformedQueryException, RepositoryException { String base = query.getBaseURI(); String sparql = bindMultiples(query.toString()); return bindSingles(con.prepareTupleQuery(SPARQL, sparql, base)); } private BooleanQuery prepareBooleanQuery() throws MalformedQueryException, RepositoryException { String base = query.getBaseURI(); String sparql = bindMultiples(query.toString()); return bindSingles(con.prepareBooleanQuery(SPARQL, sparql, base)); } private ObjectQuery prepareObjectQuery(Class<?> concept) throws MalformedQueryException, RepositoryException { String base = query.getBaseURI(); String sparql = bindMultiples(query.toObjectString(concept)); return bindSingles(con.prepareObjectQuery(SPARQL, sparql, base)); } private String bindMultiples(String sparql) { if (bindingNames.isEmpty()) return sparql; StringBuilder sb = new StringBuilder(sparql); sb.append("\nBINDINGS"); for (String name : bindingNames) { sb.append(" ?").append(name); } sb.append(" {\n"); for (List<Value> values : bindingValues) { sb.append("\t("); for (Value value : values) { if (value == null) { sb.append("UNDEF"); } else if (value instanceof URI) { writeURI(sb, value); } else if (value instanceof BNode) { writeBNode(sb, value); } else if (value instanceof Literal) { writeLiteral(sb, value); } else { throw new AssertionError(); } sb.append(" "); } sb.append(")\n"); } sb.append("}\n"); return sb.toString(); } private void writeBNode(StringBuilder sb, Value value) { sb.append("_:").append(value.stringValue()); } private void writeLiteral(StringBuilder sb, Value value) { Literal lit = (Literal) value; sb.append("\""); String label = value.stringValue(); sb.append(encodeString(label)); sb.append("\""); if (lit.getDatatype() != null) { // Append the literal's datatype (possibly written as an // abbreviated URI) sb.append("^^"); writeURI(sb, lit.getDatatype()); } if (lit.getLanguage() != null) { // Append the literal's language sb.append("@"); sb.append(lit.getLanguage()); } } private void writeURI(StringBuilder sb, Value value) { sb.append("<"); String uri = value.stringValue(); sb.append(encodeURIString(uri)); sb.append(">"); } private <T extends Operation> T bindSingles(T qry) { for (Map.Entry<String, Value> binding : bindings.entrySet()) { qry.setBinding(binding.getKey(), binding.getValue()); } return qry; } private String encodeString(String label) { label = label.replace("\\", "\\\\"); label = label.replace("\t", "\\t"); label = label.replace("\n", "\\n"); label = label.replace("\r", "\\r"); label = label.replace("\"", "\\\""); return label; } private String encodeURIString(String uri) { uri = uri.replace("\\", "\\\\"); uri = uri.replace(">", "\\>"); return uri; } } private final SparqlQuery sparql; private final String systemId; public SparqlEvaluator(SparqlQuery query) throws MalformedURLException, MalformedQueryException, IOException { this.systemId = query.getBaseURI(); sparql = query; } @Override public String toString() { return sparql.toString(); } public SparqlBuilder prepare(ObjectConnection con) { return new SparqlBuilder(con, getSparqlQuery()); } private SparqlQuery getSparqlQuery() { return sparql; } }