Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id D19ED200D7A for ; Sun, 31 Dec 2017 11:52:43 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id CFF97160C09; Sun, 31 Dec 2017 10:52:43 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 595AA160C37 for ; Sun, 31 Dec 2017 11:52:41 +0100 (CET) Received: (qmail 64394 invoked by uid 500); 31 Dec 2017 10:52:40 -0000 Mailing-List: contact commits-help@jena.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@jena.apache.org Delivered-To: mailing list commits@jena.apache.org Received: (qmail 64359 invoked by uid 99); 31 Dec 2017 10:52:40 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 31 Dec 2017 10:52:40 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 74889DFF75; Sun, 31 Dec 2017 10:52:39 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: andy@apache.org To: commits@jena.apache.org Date: Sun, 31 Dec 2017 10:52:41 -0000 Message-Id: In-Reply-To: <26a8450f3c43437ea5e547f2c6ec7345@git.apache.org> References: <26a8450f3c43437ea5e547f2c6ec7345@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [03/11] jena git commit: JENA-1454: Introduce builder pattern for result set reading and writing. archived-at: Sun, 31 Dec 2017 10:52:44 -0000 http://git-wip-us.apache.org/repos/asf/jena/blob/716b86cf/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderJSON.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderJSON.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderJSON.java new file mode 100644 index 0000000..e13cc23 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderJSON.java @@ -0,0 +1,247 @@ +/* + * 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.riot.resultset.rw; + +import static org.apache.jena.riot.resultset.rw.JSONResultsKW.*; + +import java.io.InputStream; +import java.util.*; + +import org.apache.jena.atlas.json.JSON; +import org.apache.jena.atlas.json.JsonArray; +import org.apache.jena.atlas.json.JsonObject; +import org.apache.jena.atlas.json.JsonValue; +import org.apache.jena.atlas.logging.Log; +import org.apache.jena.datatypes.RDFDatatype; +import org.apache.jena.datatypes.TypeMapper; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.NodeFactory; +import org.apache.jena.query.ARQ; +import org.apache.jena.query.ResultSet; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.riot.lang.LabelToNode; +import org.apache.jena.riot.resultset.ResultSetLang; +import org.apache.jena.riot.resultset.ResultSetReader; +import org.apache.jena.riot.resultset.ResultSetReaderFactory; +import org.apache.jena.riot.system.SyntaxLabels; +import org.apache.jena.sparql.core.Var; +import org.apache.jena.sparql.engine.QueryIterator; +import org.apache.jena.sparql.engine.ResultSetStream; +import org.apache.jena.sparql.engine.binding.Binding; +import org.apache.jena.sparql.engine.binding.BindingFactory; +import org.apache.jena.sparql.engine.binding.BindingMap; +import org.apache.jena.sparql.engine.iterator.QueryIterPlainWrapper; +import org.apache.jena.sparql.graph.GraphFactory; +import org.apache.jena.sparql.resultset.ResultSetException; +import org.apache.jena.sparql.resultset.SPARQLResult; +import org.apache.jena.sparql.util.Context; + +/** Read JSON format SPARQL Results. + *

+ * SPARQL 1.1 Query Results JSON Format + */ +public class ResultSetReaderJSON implements ResultSetReader { + + public static final ResultSetReaderFactory factory = lang -> { + if (!Objects.equals(lang, ResultSetLang.SPARQLResultSetJSON ) ) + throw new ResultSetException("ResultSet for JSON asked for a "+lang); + return new ResultSetReaderJSON(); + }; + + private ResultSetReaderJSON() {} + + // TODO Streaming version of JSON Result set processing + + @Override + public SPARQLResult readAny(InputStream in, Context context) { + return process(in, null, context); + } + + static private SPARQLResult process(InputStream in, Model model, Context context) { + if ( context == null ) + context = ARQ.getContext(); + RS_JSON exec = new RS_JSON(context); + exec.parse(in); + if ( model == null ) + model = GraphFactory.makeJenaDefaultModel(); + if ( exec.rows != null ) { + QueryIterator qIter = new QueryIterPlainWrapper(exec.rows.iterator()); + ResultSet rs = new ResultSetStream(Var.varNames(exec.vars), model, qIter); + return new SPARQLResult(rs); + } else + return new SPARQLResult(exec.booleanResult); + } + + /** + * Parse a result set - whether rows (SELECT) or a boolean results (ASK). + * This object is one-time use and exists to carry the results as they are built-up. + */ + static class RS_JSON { + final Context context; + Boolean booleanResult = null; // Valid if rows is null. + List rows = null; + List vars = null; + final LabelToNode labelMap; + + RS_JSON(Context context) { + this.context = context; + boolean inputGraphBNodeLabels = (context != null) && context.isTrue(ARQ.inputGraphBNodeLabels); + this.labelMap = inputGraphBNodeLabels + ? SyntaxLabels.createLabelToNodeAsGiven() + : SyntaxLabels.createLabelToNode(); + this.rows = null ; + } + + private void parse(InputStream in) { + JsonObject obj = JSON.parse(in); + + // Boolean? + if ( obj.hasKey(kBoolean) ) { + checkContains(obj, true, true, kHead, kBoolean); + booleanResult = obj.get(kBoolean).getAsBoolean().value(); + rows = null; + return; + } + + // ResultSet. + rows = new ArrayList<>(1000); + + checkContains(obj, true, true, kHead, kResults); + + // process head + if ( !obj.get(kHead).isObject() ) + throw new ResultSetException("Key 'head' must have a JSON object as value: found: " + obj.get(kHead)); + JsonObject head = obj.get(kHead).getAsObject(); + + // ---- Head + // -- Link - array. + if ( head.hasKey(kLink) ) { + List links = new ArrayList<>(); + + if ( head.get(kLink).isString() ) { + Log.warn(this, "Link field is a string, should be an array of strings"); + links.add(head.get(kLink).getAsString().value()); + } else { + if ( !head.get(kLink).isArray() ) + throw new ResultSetException("Key 'link' must have be an array: found: " + obj.get(kLink)); + + for ( JsonValue v : head.get(kLink).getAsArray() ) { + if ( !v.isString() ) + throw new ResultSetException("Key 'link' must have be an array of strings: found: " + v); + links.add(v.getAsString().value()); + } + } + } + // -- Vars + vars = parseVars(head); + + // ---- Results + JsonObject results = obj.get(kResults).getAsObject(); + if ( !results.get(kBindings).isArray() ) + throw new ResultSetException("'bindings' must be an array"); + JsonArray array = results.get(kBindings).getAsArray(); + Iterator iter = array.iterator(); + + for ( ; iter.hasNext() ; ) { + BindingMap b = BindingFactory.create(); + JsonValue v = iter.next(); + if ( !v.isObject() ) + throw new ResultSetException("Entry in 'bindings' array must be an object {}"); + JsonObject x = v.getAsObject(); + Set varNames = x.keys(); + for ( String vn : varNames ) { + // if ( ! vars.contains(vn) ) {} + JsonValue vt = x.get(vn); + if ( !vt.isObject() ) + throw new ResultSetException("Binding for variable '" + vn + "' is not a JSON object: " + vt); + Node n = parseOneTerm(vt.getAsObject(), labelMap); + b.add(Var.alloc(vn), n); + } + rows.add(b); + } + } + + private static List parseVars(JsonObject obj) { + if ( !obj.get(kVars).isArray() ) + throw new ResultSetException("Key 'vars' must be a JSON array"); + JsonArray a = obj.get(kVars).getAsArray(); + Iterator iter = a.iterator(); + List vars = new ArrayList<>(); + for ( ; iter.hasNext() ; ) { + JsonValue v = iter.next(); + if ( !v.isString() ) + throw new ResultSetException("Entries in vars array must be strings"); + Var var = Var.alloc(v.getAsString().value()); + vars.add(var); + } + return vars; + } + + private static Node parseOneTerm(JsonObject term, LabelToNode labelMap) { + checkContains(term, false, false, kType, kValue, kXmlLang, kDatatype); + + String type = stringOrNull(term, kType); + String v = stringOrNull(term, kValue); + + if ( kUri.equals(type) ) { + checkContains(term, false, true, kType, kValue); + String uri = v; + Node n = NodeFactory.createURI(v); + return n; + } + + if ( kLiteral.equals(type) || kTypedLiteral.equals(type) ) { + String lang = stringOrNull(term, kXmlLang); + String dtStr = stringOrNull(term, kDatatype); + if ( lang != null && dtStr != null ) + throw new ResultSetException("Both language and datatype defined: " + term); + RDFDatatype dt = TypeMapper.getInstance().getSafeTypeByName(dtStr); + return NodeFactory.createLiteral(v, lang, dt); + } + + if ( kBnode.equals(type) ) + return labelMap.get(null, v); + + throw new ResultSetException("Object key not recognized as valid for an RDF term: " + term); + } + + private static String stringOrNull(JsonObject obj, String key) { + JsonValue v = obj.get(key); + if ( v == null ) + return null; + if ( !v.isString() ) + throw new ResultSetException("Not a string: key: " + key); + return v.getAsString().value(); + } + + private static void checkContains(JsonObject term, boolean allowUndefinedKeys, boolean requireAllExpectedKeys, String... keys) { + List expectedKeys = Arrays.asList(keys); + Set declared = new HashSet<>(); + for ( String k : term.keys() ) { + if ( !expectedKeys.contains(k) && !allowUndefinedKeys ) + throw new ResultSetException("Expected only object keys " + Arrays.asList(keys) + " but encountered '" + k + "'"); + if ( expectedKeys.contains(k) ) + declared.add(k); + } + + if ( requireAllExpectedKeys && declared.size() < expectedKeys.size() ) + throw new ResultSetException("One or more of the required keys " + expectedKeys + " was not found"); + } + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/716b86cf/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderThrift.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderThrift.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderThrift.java new file mode 100644 index 0000000..bc7523f --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderThrift.java @@ -0,0 +1,60 @@ +/* + * 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.riot.resultset.rw; + +import java.io.InputStream; +import java.io.Reader; +import java.util.Objects; + +import org.apache.jena.atlas.lib.NotImplemented; +import org.apache.jena.query.ResultSet; +import org.apache.jena.riot.resultset.ResultSetLang; +import org.apache.jena.riot.resultset.ResultSetReader; +import org.apache.jena.riot.resultset.ResultSetReaderFactory; +import org.apache.jena.riot.thrift.BinRDF; +import org.apache.jena.sparql.resultset.ResultSetException; +import org.apache.jena.sparql.resultset.SPARQLResult; +import org.apache.jena.sparql.util.Context; + +public class ResultSetReaderThrift implements ResultSetReader { + + public static ResultSetReaderFactory factory = lang->{ + if (!Objects.equals(lang, ResultSetLang.SPARQLResultSetThrift ) ) + throw new ResultSetException("ResultSetReadernot for Thrift asked for a "+lang); + return new ResultSetReaderThrift(); + }; + + private ResultSetReaderThrift() {} + + @Override + public ResultSet read(InputStream in, Context context) { + return BinRDF.readResultSet(in); + } + + @Override + public ResultSet read(Reader in, Context context) { + throw new NotImplemented("Reading binary data from a java.io.Reader is not possible"); + } + + @Override + public SPARQLResult readAny(InputStream in, Context context) { + return new SPARQLResult(read(in, context)); + } + +} http://git-wip-us.apache.org/repos/asf/jena/blob/716b86cf/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderXML.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderXML.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderXML.java new file mode 100644 index 0000000..65a2a7c --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderXML.java @@ -0,0 +1,54 @@ +/* + * 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.riot.resultset.rw; + +import java.io.InputStream; +import java.io.Reader; +import java.util.Objects; + +import org.apache.jena.query.ResultSet; +import org.apache.jena.riot.resultset.ResultSetLang; +import org.apache.jena.riot.resultset.ResultSetReader; +import org.apache.jena.riot.resultset.ResultSetReaderFactory; +import org.apache.jena.sparql.resultset.ResultSetException; +import org.apache.jena.sparql.resultset.SPARQLResult; +import org.apache.jena.sparql.util.Context; + +public class ResultSetReaderXML implements ResultSetReader { + + public static final ResultSetReaderFactory factory = lang -> { + if (!Objects.equals(lang, ResultSetLang.SPARQLResultSetXML ) ) + throw new ResultSetException("ResultSet for XML asked for a "+lang); + return new ResultSetReaderXML(); + }; + + private ResultSetReaderXML() {} + + @Override + public SPARQLResult readAny(InputStream in, Context context) { + SPARQLResult result = ResultsStAX.read(in, null, context); + return result; + } + + @Override + public ResultSet read(Reader in, Context context) { + SPARQLResult result = ResultsStAX.read(in, null, context); + return result.getResultSet(); + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/716b86cf/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON.java new file mode 100644 index 0000000..8291b03 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterJSON.java @@ -0,0 +1,305 @@ +/* + * 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.riot.resultset.rw; + +import static org.apache.jena.riot.resultset.rw.JSONResultsKW.*; + +import java.io.OutputStream; +import java.io.Writer; +import java.util.Iterator; +import java.util.Objects; + +import org.apache.jena.atlas.io.IO; +import org.apache.jena.atlas.io.IndentedWriter; +import org.apache.jena.atlas.json.io.JSWriter; +import org.apache.jena.atlas.logging.Log; +import org.apache.jena.query.ARQ; +import org.apache.jena.query.QuerySolution; +import org.apache.jena.query.ResultSet; +import org.apache.jena.rdf.model.Literal; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.impl.Util; +import org.apache.jena.riot.out.NodeToLabel; +import org.apache.jena.riot.resultset.ResultSetLang; +import org.apache.jena.riot.resultset.ResultSetWriter; +import org.apache.jena.riot.resultset.ResultSetWriterFactory; +import org.apache.jena.riot.system.SyntaxLabels; +import org.apache.jena.sparql.resultset.ResultSetApply; +import org.apache.jena.sparql.resultset.ResultSetException; +import org.apache.jena.sparql.resultset.ResultSetProcessor; +import org.apache.jena.sparql.util.Context; + +public class ResultSetWriterJSON implements ResultSetWriter { + + public static ResultSetWriterFactory factory = lang->{ + if (!Objects.equals(lang, ResultSetLang.SPARQLResultSetJSON ) ) + throw new ResultSetException("ResultSetWriter for JSON asked for a "+lang); + return new ResultSetWriterJSON(); + }; + + private ResultSetWriterJSON() {} + + @Override + public void write(Writer out, ResultSet resultSet, Context context) { + throw new UnsupportedOperationException("Writing JSON results to a java.io.Writer. Use an OutputStream.") ; + } + + @Override + public void write(OutputStream outStream, boolean result, Context context) { + JSWriter out = new JSWriter(outStream); + out.startOutput(); + out.startObject(); + out.key(kHead); + out.startObject(); + out.finishObject(); + out.pair(kBoolean, result); + out.finishObject(); + out.finishOutput(); + IO.flush(outStream); + } + + @Override + public void write(OutputStream out, ResultSet resultSet, Context context) { + JSONOutputResultSet jsonOut = new JSONOutputResultSet(out, context); + ResultSetApply a = new ResultSetApply(resultSet, jsonOut); + a.apply(); + } + + private static class JSONOutputResultSet implements ResultSetProcessor { + private static final boolean multiLineValues = false; + private static final boolean multiLineVarNames = false; + + private final IndentedWriter out; + private final NodeToLabel labels; + + private JSONOutputResultSet(OutputStream outStream, Context context) { + this(new IndentedWriter(outStream), context); + } + + private JSONOutputResultSet(IndentedWriter indentedOut, Context context) { + out = indentedOut; + boolean outputGraphBNodeLabels = (context != null) && context.isTrue(ARQ.outputGraphBNodeLabels); + labels = outputGraphBNodeLabels + ? SyntaxLabels.createNodeToLabelAsGiven() + : SyntaxLabels.createNodeToLabel(); + } + + @Override + public void start(ResultSet rs) { + println("{"); + out.incIndent(); + doHead(rs); + println(quoteName(kResults), ": {"); + out.incIndent(); + println(quoteName(kBindings), ": ["); + out.incIndent(); + firstSolution = true; + } + + @Override + public void finish(ResultSet rs) { + // Close last binding. + out.println(); + + out.decIndent(); // bindings + println("]"); + out.decIndent(); + println("}"); // results + out.decIndent(); + println("}"); // top level {} + out.flush(); + } + + private void doHead(ResultSet rs) { + println(quoteName(kHead),": {"); + out.incIndent(); + doLink(rs); + doVars(rs); + out.decIndent(); + println("} ,"); + } + + private void doLink(ResultSet rs) { + // ---- link + // out.println("\"link\": []") ; + } + + private void doVars(ResultSet rs) { + // On one line. + print(quoteName(kVars), ": [ "); + if ( multiLineVarNames ) + out.println(); + out.incIndent(); + for ( Iterator iter = rs.getResultVars().iterator() ; iter.hasNext() ; ) { + String varname = iter.next(); + print("\"", varname, "\""); + if ( multiLineVarNames ) + println(); + if ( iter.hasNext() ) + print(" , "); + } + println(" ]"); + out.decIndent(); + } + + boolean firstSolution = true; + boolean firstBindingInSolution = true; + + // NB assumes are on end of previous line. + @Override + public void start(QuerySolution qs) { + if ( !firstSolution ) + println(" ,"); + firstSolution = false; + println("{"); + out.incIndent(); + firstBindingInSolution = true; + } + + @Override + public void finish(QuerySolution qs) { + println(); // Finish last binding + out.decIndent(); + print("}"); // NB No newline + } + + @Override + public void binding(String varName, RDFNode value) { + if ( value == null ) + return; + + if ( !firstBindingInSolution ) + println(" ,"); + firstBindingInSolution = false; + + // Do not use quoteName - varName may not be JSON-safe as a bare name. + print(quote(varName), ": { "); + if ( multiLineValues ) + out.println(); + + out.incIndent(); + // Old, explicit unbound + // if ( value == null ) + // printUnbound() ; + // else + if ( value.isLiteral() ) + printLiteral((Literal)value); + else if ( value.isResource() ) + printResource((Resource)value); + else + Log.warn(this, "Unknown RDFNode type in result set: " + value.getClass()); + out.decIndent(); + + if ( !multiLineValues ) + print(" "); + print("}"); // NB No newline + } + + private void printUnbound() { + print(quoteName(kType), ": ", quote(kUnbound), " , ") ; + if ( multiLineValues ) + println() ; + print(quoteName(kValue), ": null") ; + if ( multiLineValues ) + println() ; + } + + private void printLiteral(Literal literal) { + String datatype = literal.getDatatypeURI(); + String lang = literal.getLanguage(); + + if ( Util.isSimpleString(literal) || Util.isLangString(literal) ) { + print(quoteName(kType), ": ", quote(kLiteral), " , "); + if ( multiLineValues ) + println(); + + if ( lang != null && !lang.equals("") ) { + print(quoteName(kXmlLang), ": ", quote(lang), " , "); + if ( multiLineValues ) + println(); + } + } else { + print(quoteName(kType), ": ", quote(kLiteral), " , "); + if ( multiLineValues ) + println(); + + print(quoteName(kDatatype), ": ", quote(datatype), " , "); + if ( multiLineValues ) + println(); + } + + print(quoteName(kValue), ": ", quote(literal.getLexicalForm())); + if ( multiLineValues ) + println(); + } + + private void printResource(Resource resource) { + if ( resource.isAnon() ) { + String label = labels.get(null, resource.asNode()); + // Comes with leading "_:" + label = label.substring(2); + + print(quoteName(kType), ": ", quote(kBnode), " , "); + if ( multiLineValues ) + out.println(); + + print(quoteName(kValue), ": ", quote(label)); + + if ( multiLineValues ) + println(); + } else { + print(quoteName(kType), ": ", quote(kUri), " , "); + if ( multiLineValues ) + println(); + print(quoteName(kValue), ": ", quote(resource.getURI())); + if ( multiLineValues ) + println(); + return; + } + } + + private void print(String... strings) { + for ( String s : strings ) + out.print(s); + } + + private void println(String... strings) { + print(strings); + out.println(); + } + + private static String quote(String string) { + // Scope for efficiency improvement. + return JSWriter.outputQuotedString(string); + } + + // Quote a name (known to be JSON-safe) + // Never the RHS of a member entry (for example "false") + // Some (the Java JSON code for one) JSON parsers accept an unquoted + // string as a name of a name/value pair. + + private static String quoteName(String string) { + // All calls to quoteName are builtin keywords which are already safe. + // but need the "" added. + //return "\""+string+"\""; + return quote(string); + } + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/716b86cf/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterThrift.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterThrift.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterThrift.java new file mode 100644 index 0000000..ee5aef5 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterThrift.java @@ -0,0 +1,54 @@ +/* + * 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.riot.resultset.rw; + +import java.io.OutputStream; +import java.io.Writer; +import java.util.Objects; + +import org.apache.jena.atlas.lib.NotImplemented; +import org.apache.jena.query.ResultSet; +import org.apache.jena.riot.resultset.ResultSetLang; +import org.apache.jena.riot.resultset.ResultSetWriter; +import org.apache.jena.riot.resultset.ResultSetWriterFactory; +import org.apache.jena.riot.thrift.BinRDF; +import org.apache.jena.sparql.resultset.ResultSetException; +import org.apache.jena.sparql.util.Context; + +public class ResultSetWriterThrift implements ResultSetWriter { + + public static ResultSetWriterFactory factory = lang -> { + if (!Objects.equals(lang, ResultSetLang.SPARQLResultSetThrift ) ) + throw new ResultSetException("ResultSetWriter for RDF/Thift asked for a "+lang); + return new ResultSetWriterThrift(); + }; + + @Override + public void write(OutputStream out, ResultSet resultSet, Context context) + { BinRDF.writeResultSet(out, resultSet) ; } + + @Override + public void write(Writer out, ResultSet resultSet, Context context) { + throw new NotImplemented("Writing binary data to a java.io.Writer is not possible") ; + } + + @Override + public void write(OutputStream out, boolean result, Context context) + { throw new NotImplemented("No Thrift RDF encoding defined for boolean results"); } +} http://git-wip-us.apache.org/repos/asf/jena/blob/716b86cf/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterXML.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterXML.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterXML.java new file mode 100644 index 0000000..92fd5c4 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetWriterXML.java @@ -0,0 +1,380 @@ +/* + * 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.riot.resultset.rw; + +import java.io.OutputStream; +import java.io.Writer; +import java.util.Objects; + +import org.apache.jena.atlas.io.IndentedWriter; +import org.apache.jena.atlas.logging.Log; +import org.apache.jena.query.ARQ; +import org.apache.jena.query.QuerySolution; +import org.apache.jena.query.ResultSet; +import org.apache.jena.rdf.model.Literal; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.impl.Util; +import org.apache.jena.riot.out.NodeToLabel; +import org.apache.jena.riot.resultset.ResultSetLang; +import org.apache.jena.riot.resultset.ResultSetWriter; +import org.apache.jena.riot.resultset.ResultSetWriterFactory; +import org.apache.jena.riot.system.SyntaxLabels; +import org.apache.jena.sparql.SystemARQ; +import org.apache.jena.sparql.resultset.ResultSetApply; +import org.apache.jena.sparql.resultset.ResultSetException; +import org.apache.jena.sparql.resultset.ResultSetProcessor; +import org.apache.jena.sparql.util.Context; +import org.apache.jena.sparql.util.Symbol; + +public class ResultSetWriterXML implements ResultSetWriter { + + public static final Symbol xmlInstruction = SystemARQ.allocSymbol("xmlInstruction"); + public static final Symbol xmlStylesheet = SystemARQ.allocSymbol("xmlStylesheet"); + + public static ResultSetWriterFactory factory = lang->{ + if (!Objects.equals(lang, ResultSetLang.SPARQLResultSetXML ) ) + throw new ResultSetException("ResultSetWriter for XML asked for a "+lang); + return new ResultSetWriterXML(); + }; + + private ResultSetWriterXML() {} + + @Override + public void write(Writer out, ResultSet resultSet, Context context) { + throw new UnsupportedOperationException("Writing XML results to a java.io.Writer. Use an OutputStream.") ; + } + + @Override + public void write(OutputStream outStream, boolean result, Context context) { + XMLOutputASK out = new XMLOutputASK(outStream); + if ( context != null && context.isDefined(xmlInstruction) ) + out.xmlInst = context.isTrue(xmlInstruction); + if ( context != null && context.isDefined(xmlStylesheet) ) + out.stylesheetURL = (String)(context.get(xmlStylesheet)); + out.exec(result); + } + + @Override + public void write(OutputStream outStream, ResultSet resultSet, Context context) { + XMLOutputResultSet xOut = new XMLOutputResultSet(outStream, context); + if ( context != null && context.isDefined(xmlInstruction) ) + xOut.setXmlInst(context.isTrue(xmlInstruction)); + if ( context != null && context.isDefined(xmlStylesheet) ) + xOut.setStylesheetURL((String)(context.get(xmlStylesheet))); + ResultSetApply a = new ResultSetApply(resultSet, xOut); + a.apply(); + } + + private class XMLOutputASK implements XMLResults { + String stylesheetURL = null; + IndentedWriter out; + int bNodeCounter = 0; + boolean xmlInst = true; + + public XMLOutputASK(OutputStream outStream) { + this(outStream, null); + } + + public XMLOutputASK(OutputStream outStream, String stylesheetURL) { + this(new IndentedWriter(outStream), stylesheetURL); + } + + public XMLOutputASK(IndentedWriter indentedOut, String stylesheetURL) { + out = indentedOut; + this.stylesheetURL = stylesheetURL; + } + + public void exec(boolean result) { + if ( xmlInst ) + out.println(""); + + if ( stylesheetURL != null ) + out.println(""); + + out.println("<" + dfRootTag + " xmlns=\"" + dfNamespace + "\">"); + out.incIndent(INDENT); + + // Head + out.println("<" + dfHead + ">"); + out.incIndent(INDENT); + if ( false ) { + String link = "UNSET"; + out.println(""); + } + out.decIndent(INDENT); + out.println(""); + + if ( result ) + out.println("true"); + else + out.println("false"); + out.decIndent(INDENT); + out.println(""); + out.flush(); + } + } + + private static class XMLOutputResultSet implements ResultSetProcessor, XMLResults + { + private static boolean outputExplicitUnbound = false ; + + private int index = 0 ; // First index is 1 + private String stylesheetURL = null ; + private boolean xmlInst = true ; + + private final IndentedWriter out ; + private int bNodeCounter = 0 ; + private final NodeToLabel bNodeMap; + + private XMLOutputResultSet(OutputStream outStream, Context context) { + this(new IndentedWriter(outStream), context); + } + + private XMLOutputResultSet(IndentedWriter indentedOut, Context context) { + out = indentedOut; + boolean outputGraphBNodeLabels = (context != null) && context.isTrue(ARQ.outputGraphBNodeLabels); + bNodeMap = outputGraphBNodeLabels + ? SyntaxLabels.createNodeToLabelAsGiven() + : SyntaxLabels.createNodeToLabel(); + } + + @Override + public void start(ResultSet rs) { + if ( xmlInst ) + out.println(""); + + if ( stylesheetURL != null ) { + out.print(""); + } + + // ---- Root + out.print("<"); + out.print(dfRootTag); + out.print(" xmlns=\""); + out.print(dfNamespace); + out.println("\">"); + + // ---- Header + + out.incIndent(INDENT); + out.print("<"); + out.print(dfHead); + out.println(">"); + + if ( false ) { + String link = "UNSET"; + out.print(""); + } + + for ( String n : rs.getResultVars() ) { + out.incIndent(INDENT); + out.print("<"); + out.print(dfVariable); + out.print(" "); + out.print(dfAttrVarName); + out.print("=\""); + out.print(n); + out.print("\""); + out.println("/>"); + out.decIndent(INDENT); + } + out.print(""); + out.decIndent(INDENT); + + // Start results proper + out.incIndent(INDENT); + out.print("<"); + out.print(dfResults); + out.println(">"); + out.incIndent(INDENT); + } + + @Override + public void finish(ResultSet rs) { + out.decIndent(INDENT); + out.print(""); + out.decIndent(INDENT); + out.print(""); + out.flush(); + } + + @Override + public void start(QuerySolution qs) { + out.print("<"); + out.print(dfSolution); + out.println(">"); + index++; + out.incIndent(INDENT); + } + + @Override + public void finish(QuerySolution qs) { + out.decIndent(INDENT); + out.print(""); + } + + @Override + public void binding(String varName, RDFNode node) { + if ( node == null && !outputExplicitUnbound ) + return; + + out.print("<"); + out.print(dfBinding); + out.print(" name=\""); + out.print(varName); + out.println("\">"); + out.incIndent(INDENT); + printBindingValue(node); + out.decIndent(INDENT); + out.print(""); + } + + void printBindingValue(RDFNode node) { + if ( node == null ) { + // Unbound + out.print("<"); + out.print(dfUnbound); + out.println("/>"); + return; + } + + if ( node instanceof Literal ) { + printLiteral((Literal)node); + return; + } + + if ( node instanceof Resource ) { + printResource((Resource)node); + return; + } + + Log.warn(this, "Unknown RDFNode type in result set: " + node.getClass()); + } + + void printLiteral(Literal literal) { + out.print("<"); + out.print(dfLiteral); + + if ( Util.isLangString(literal) ) { + String lang = literal.getLanguage(); + out.print(" xml:lang=\""); + out.print(literal.getLanguage()); + out.print("\""); + } else if ( !Util.isSimpleString(literal) ) { + // Datatype + // (RDF 1.1) not xsd:string nor rdf:langString. + // (RDF 1.0) any datatype. + String datatype = literal.getDatatypeURI(); + out.print(" "); + out.print(dfAttrDatatype); + out.print("=\""); + out.print(datatype); + out.print("\""); + } + + out.print(">"); + out.print(xml_escape(literal.getLexicalForm())); + out.print(""); + } + + void printResource(Resource r) { + if ( r.isAnon() ) { + String label = bNodeMap.get(null, r.asNode()); + // Comes with leading "_:" + label = label.substring(2); + out.print("<"); + out.print(dfBNode); + out.print(">"); + out.print(xml_escape(label)); + out.print(""); + } else { + out.print("<"); + out.print(dfURI); + out.print(">"); + out.print(xml_escape(r.getURI())); + out.print(""); + } + } + + private static String xml_escape(String string) { + final StringBuilder sb = new StringBuilder(string); + + int offset = 0; + String replacement; + char found; + for (int i = 0; i < string.length(); i++) { + found = string.charAt(i); + + switch (found) { + case '&' : replacement = "&"; break; + case '<' : replacement = "<"; break; + case '>' : replacement = ">"; break; + case '\r': replacement = " "; break; + case '\n': replacement = " "; break; + default : replacement = null; + } + + if (replacement != null) { + sb.replace(offset + i, offset + i + 1, replacement); + offset += replacement.length() - 1; // account for added chars + } + } + + return sb.toString(); + } + + /** @return Returns the stylesheetURL. */ + public String getStylesheetURL() + { return stylesheetURL ; } + + /** @param stylesheetURL The stylesheetURL to set. */ + public void setStylesheetURL(String stylesheetURL) + { this.stylesheetURL = stylesheetURL ; } + + /** @return Returns the xmlInst. */ + public boolean getXmlInst() + { return xmlInst ; } + + /** @param xmlInst The xmlInst to set. */ + public void setXmlInst(boolean xmlInst) + { this.xmlInst = xmlInst ; } + } + +} http://git-wip-us.apache.org/repos/asf/jena/blob/716b86cf/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsMgrX.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsMgrX.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsMgrX.java new file mode 100644 index 0000000..80ee9b0 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsMgrX.java @@ -0,0 +1,69 @@ +/* + * 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.riot.resultset.rw; + +import java.io.OutputStream; + +import org.apache.jena.query.ResultSet; +import org.apache.jena.sparql.resultset.ResultSetException; +import org.apache.jena.sparql.resultset.SPARQLResult; + +public class ResultsMgrX { + // For org.apache.jena.riotResultSetMgr + public static ResultSet readResults(String url) { + ResultSet rs = read(url).getResultSet(); + if ( rs == null ) + throw new ResultSetException("Not a result set"); + return rs; + } + + public static boolean readBoolean(String url) { + Boolean b = read(url).getBooleanResult(); + if ( b == null ) + throw new ResultSetException("Not a boolean result"); + return b; + } + + private static SPARQLResult read(String url) { + return null; +// ResultsReader.create() +// //.forceLang(lang) +// //.context(context) +// .build() +// .read(url); + } + + public static void write(OutputStream output, ResultSet resultSet) { + ResultsWriter.create() + .build() + .write(output, resultSet); + } + + public static void write(OutputStream output, Boolean booleanResult) { + } + + public static void write(String filename, ResultSet resultSet) { + ResultsWriter.create() + .build() + .write(filename, resultSet); + } + + public static void write(String filename, Boolean booleanResult) { + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/716b86cf/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsReader.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsReader.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsReader.java new file mode 100644 index 0000000..ce76d8c --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsReader.java @@ -0,0 +1,151 @@ +/* + * 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.riot.resultset.rw; + +import java.io.InputStream; + +import org.apache.jena.atlas.web.ContentType; +import org.apache.jena.atlas.web.TypedInputStream; +import org.apache.jena.query.ResultSet; +import org.apache.jena.riot.*; +import org.apache.jena.riot.resultset.ResultSetReader; +import org.apache.jena.riot.resultset.ResultSetReaderFactory; +import org.apache.jena.riot.resultset.ResultSetReaderRegistry; +import org.apache.jena.riot.system.stream.StreamManager; +import org.apache.jena.sparql.resultset.SPARQLResult; +import org.apache.jena.sparql.util.Context; + +public class ResultsReader { + + /** Create a {@code ResultsReader.Builder}. */ + public static Builder create() { return new Builder() ; } + + public static class Builder { + private Lang hintLang = null; + private Lang forceLang = null; + private Context context = null; + + /** Provide a {@link Lang} for the parser. + * The declared MIME type takes precedence, + * the file extension does not. + */ + public Builder lang(Lang hintLang) { + this.hintLang = hintLang; + return this; + } + + /** Provide a {@link Lang} for the parser. + * This setting overrides any declared MIME type or file extension. + */ + public Builder forceLang(Lang forceLang) { + this.forceLang = forceLang; + return this; + } + + /** Set the {@link Context}. This defaults to the global settings of {@code ARQ.getContext()}. */ + public Builder context(Context context) { + if ( context != null ) + context = context.copy(); + this.context = context; + return this; + } + + /** Build a {@code ResultsReader} */ + public ResultsReader build() { + return new ResultsReader(hintLang, forceLang, context); + } + + /** Short form equivalent to {@code .build().read(url)} */ + public ResultSet read(String url) { + return build().read(url); + } + + /** Short form equivalent to {@code .build().read(InputStreams)} */ + public ResultSet read(InputStream input) { + return build().read(input); + } + } + + private final Lang hintLang; + private final Lang forceLang; + private final Context context; + + private ResultsReader(Lang hintLang, Lang forceLang, Context context) { + super(); + this.hintLang = hintLang; + this.forceLang = forceLang; + this.context = context; + } + + private Lang determinLang(TypedInputStream in, String url) { + if ( in == null ) + throw new RiotNotFoundException(url); + Lang lang = forceLang; + if ( lang == null ) { + ContentType ct = WebContent.determineCT(in.getContentType(), hintLang, url); + lang = RDFLanguages.contentTypeToLang(ct); + } + if ( lang == null ) + throw new RiotException("Can't indentify the result set syntax from "+url); + return lang; + } + + public ResultSet read(String url) { + // Remap? + TypedInputStream in = StreamManager.get(context).open(url); + Lang lang = determinLang(in, url); + return readResultSet(in.getInputStream(), lang); + } + + public ResultSet read(InputStream input) { + Lang lang = forceLang!=null ? forceLang : hintLang; + if ( lang == null ) + throw new RiotException("Need a syntax to read a result set from an InputStream"); + return readResultSet(input, lang); + } + + public SPARQLResult readAny(String url) { + TypedInputStream in = StreamManager.get(context).open(url); + Lang lang = determinLang(in, url); + return readAny(in.getInputStream(), lang); + } + + public SPARQLResult readAny(InputStream input) { + Lang lang = forceLang!=null ? forceLang : hintLang; + if ( lang == null ) + throw new RiotException("Need a syntax to read a result set from an InputStream"); + return readAny(input, lang); + } + + private ResultSet readResultSet(InputStream input, Lang lang) { + return readAny(input, lang).getResultSet(); + } + + private SPARQLResult readAny(InputStream input, Lang lang) { + if ( ! ResultSetReaderRegistry.isRegistered(lang) ) + throw new RiotException("Not registered as a SPARQL result set input syntax: "+lang); + + ResultSetReaderFactory factory = ResultSetReaderRegistry.getFactory(lang); + if ( factory == null ) + throw new RiotException("No ResultSetReaderFactory for "+lang); + ResultSetReader reader = factory.create(lang); + SPARQLResult rs = reader.readAny(input, context); + return rs; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/716b86cf/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsStAX.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsStAX.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsStAX.java new file mode 100644 index 0000000..b6dde3a --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsStAX.java @@ -0,0 +1,473 @@ +/* + * 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.riot.resultset.rw; + +import java.io.InputStream; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.apache.jena.atlas.lib.Closeable; +import org.apache.jena.atlas.logging.Log; +import org.apache.jena.datatypes.RDFDatatype; +import org.apache.jena.datatypes.TypeMapper; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.NodeFactory; +import org.apache.jena.query.ARQ; +import org.apache.jena.query.QuerySolution; +import org.apache.jena.query.ResultSet; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.riot.lang.LabelToNode; +import org.apache.jena.riot.system.SyntaxLabels; +import org.apache.jena.sparql.ARQConstants; +import org.apache.jena.sparql.core.ResultBinding; +import org.apache.jena.sparql.core.Var; +import org.apache.jena.sparql.engine.binding.Binding; +import org.apache.jena.sparql.engine.binding.BindingFactory; +import org.apache.jena.sparql.engine.binding.BindingMap; +import org.apache.jena.sparql.graph.GraphFactory; +import org.apache.jena.sparql.resultset.ResultSetException; +import org.apache.jena.sparql.resultset.SPARQLResult; +import org.apache.jena.sparql.util.Context; + +/** Public only for use by XMLOuptu (legacy) */ +public class ResultsStAX implements ResultSet, Closeable { + public static SPARQLResult read(InputStream in, Model model, Context context) { + XMLInputFactory xf = XMLInputFactory.newInstance() ; + try { + XMLStreamReader xReader = xf.createXMLStreamReader(in) ; + return worker(xReader, model, context); + } catch (XMLStreamException e) { + throw new ResultSetException("Can't initialize StAX parsing engine", e) ; + } catch (Exception ex) { + throw new ResultSetException("Failed when initializing the StAX parsing engine", ex) ; + } + } + + public static SPARQLResult read(Reader in, Model model, Context context) { + XMLInputFactory xf = XMLInputFactory.newInstance() ; + try { + XMLStreamReader xReader = xf.createXMLStreamReader(in) ; + return worker(xReader, model, context) ; + } catch (XMLStreamException e) { + throw new ResultSetException("Can't initialize StAX parsing engine", e) ; + } catch (Exception ex) { + throw new ResultSetException("Failed when initializing the StAX parsing engine", ex) ; + } + } + + private static SPARQLResult worker(XMLStreamReader xReader, Model model, Context context) { + if ( model == null ) + model = GraphFactory.makeJenaDefaultModel() ; + ResultsStAX rss = new ResultsStAX(xReader, model, context) ; + return rss.read(); + } + + // ResultSet variables + private QuerySolution current = null; + private XMLStreamReader parser = null; + private List variables = new ArrayList<>(); + private Binding binding = null; // Current + private boolean inputGraphLabels = ARQ.isTrue(ARQ.inputGraphBNodeLabels); + + private final LabelToNode bNodes; + + private boolean isResultSet = false; + + // Result set + private boolean ordered = false; + private boolean distinct = false; + private boolean finished = false; + private Model model = null; + private int row = 0; + + private boolean askResult = false; + + private ResultsStAX(XMLStreamReader reader, Model model, Context context) { + + + + parser = reader ; + this.model = model ; + boolean inputGraphBNodeLabels = (context != null) && context.isTrue(ARQ.inputGraphBNodeLabels); + this.bNodes = inputGraphBNodeLabels + ? SyntaxLabels.createLabelToNodeAsGiven() + : SyntaxLabels.createLabelToNode(); + init() ; + } + + private SPARQLResult read() { + if ( isResultSet ) + return new SPARQLResult(this); + else + return new SPARQLResult(askResult); + } + + private void init() { + try { + // Because all the tags are different, we could use one big + // switch statement! + skipTo(XMLResults.dfHead) ; + processHead() ; + skipTo(new String[]{XMLResults.dfResults, XMLResults.dfBoolean}, new String[]{XMLResults.dfResults}) ; + // Next should be a , element or + + // Need to decide what sort of thing we are reading. + + String tag = parser.getLocalName() ; + if ( tag.equals(XMLResults.dfResults) ) { + isResultSet = true ; + processResults() ; + } + if ( tag.equals(XMLResults.dfBoolean) ) { + isResultSet = false ; + processBoolean() ; + } + + } catch (XMLStreamException ex) { + Log.warn(this, "XMLStreamException: " + ex.getMessage(), ex) ; + } + } + + @Override + public boolean hasNext() { + if ( !isResultSet ) + throw new ResultSetException("Not an XML result set") ; + + if ( finished ) + return false ; + + try { + if ( binding == null ) + binding = getOneSolution() ; + } catch (XMLStreamException ex) { + staxError("XMLStreamException: " + ex.getMessage(), ex) ; + } + boolean b = (binding != null) ; + if ( !b ) + close() ; + return b ; + } + + @Override + public QuerySolution next() { + return nextSolution() ; + } + + @Override + public Binding nextBinding() { + if ( finished ) + throw new NoSuchElementException("End of XML Results") ; + if ( !hasNext() ) + throw new NoSuchElementException("End of XML Results") ; + Binding r = binding ; + row++ ; + binding = null ; + return r ; + } + + @Override + public QuerySolution nextSolution() { + Binding r = nextBinding() ; + ResultBinding currentEnv = new ResultBinding(model, r) ; + return currentEnv ; + } + + @Override + public int getRowNumber() { + return row ; + } + + @Override + public List getResultVars() { + return variables ; + } + + public boolean isOrdered() { + return ordered ; + } + + public boolean isDistinct() { + return distinct ; + } + + // No model - it was from a stream + @Override + public Model getResourceModel() { + return null ; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(ResultsStAX.class.getName()) ; + } + + @Override + public void close() { + if ( finished ) + return ; + finished = true ; + try { parser.close() ; } catch (XMLStreamException ex) {} + } + + // -------- Boolean stuff + + private void processBoolean() throws XMLStreamException { + try { + // At start of + String s = parser.getElementText() ; + if ( s.equalsIgnoreCase("true") ) { + askResult = true ; + return ; + } + if ( s.equalsIgnoreCase("false") ) { + askResult = false ; + return ; + } + throw new ResultSetException("Unknown boolean value: " + s) ; + } finally { + close() ; + } + } + + // -------- + + private void skipTo(String tag1) throws XMLStreamException { + skipTo(new String[]{tag1}, null) ; + } + + private void skipTo(String[] startElementNames, String[] stopElementNames) throws XMLStreamException { + boolean found = false ; + loop : while (parser.hasNext()) { + int event = parser.next() ; + switch (event) { + case XMLStreamConstants.END_DOCUMENT : + break loop ; + case XMLStreamConstants.END_ELEMENT : + if ( stopElementNames == null ) + break ; + + String endTag = parser.getLocalName() ; + if ( endTag != null && containsName(stopElementNames, endTag) ) + return ; + break ; + case XMLStreamConstants.START_ELEMENT : + if ( startElementNames == null ) + break ; + QName qname = parser.getName() ; + if ( !qname.getNamespaceURI().equals(XMLResults.baseNamespace) ) + staxError("skipToHead: Unexpected tag: " + qname) ; + if ( containsName(startElementNames, qname.getLocalPart()) ) + return ; + break ; + default : + // Skip stuff + } + } + + if ( !found ) { + String s1 = "" ; + if ( startElementNames != null ) + s1 = String.join(", ", startElementNames) ; + + String s2 = "" ; + if ( stopElementNames != null ) + s2 = String.join(", ", stopElementNames) ; + Log.warn(this, "Failed to find start and stop of specified elements: " + s1 + " :: " + s2) ; + } + } + + private boolean containsName(String[] elementNames, String eName) { + for ( String s : elementNames ) + { + if ( s.equals( eName ) ) + { + return true; + } + } + return false ; + } + + private void processHead() throws XMLStreamException { + // Should be at the start of head + + loop : while (parser.hasNext()) { + int event = parser.next() ; + String tag = null ; + + switch (event) { + case XMLStreamConstants.END_DOCUMENT : + break loop ; + case XMLStreamConstants.END_ELEMENT : + tag = parser.getLocalName() ; + if ( isTag(tag, XMLResults.dfHead) ) + break loop ; + break ; + case XMLStreamConstants.START_ELEMENT : + tag = parser.getLocalName() ; + if ( isTag(tag, XMLResults.dfHead) ) + break ; // This switch statement + if ( isTag(tag, XMLResults.dfVariable) ) { + String varname = parser.getAttributeValue(null, XMLResults.dfAttrVarName) ; + variables.add(varname) ; + break ; + } + if ( isTag(tag, XMLResults.dfLink) ) + break ; + + staxError("Unknown XML element: " + tag) ; + break ; + default : + } + } + } + + // -------- Result Set + + private void processResults() { + return ; + } + + + static final String XML_NS = ARQConstants.XML_NS ; + + private Binding getOneSolution() throws XMLStreamException { + if ( finished ) + return null ; + // At the start of + BindingMap binding = BindingFactory.create() ; + String varName = null ; + while (parser.hasNext()) { + int event = parser.next() ; + String tag = null ; + + switch (event) { + case XMLStreamConstants.END_DOCUMENT : + staxError("End of document while processing solution") ; + return null ; + case XMLStreamConstants.END_ELEMENT : + tag = parser.getLocalName() ; + if ( isTag(tag, XMLResults.dfSolution) ) + return binding ; + if ( isTag(tag, XMLResults.dfResults) ) + // Hit the end of solutions. + return null ; + break ; + case XMLStreamConstants.START_ELEMENT : + tag = parser.getLocalName() ; + if ( isTag(tag, XMLResults.dfSolution) ) { + binding = BindingFactory.create() ; + break ; + } + if ( isTag(tag, XMLResults.dfBinding) ) { + varName = parser.getAttributeValue(null, XMLResults.dfAttrVarName) ; + break ; + } + // URI, literal, bNode, unbound. + if ( isTag(tag, XMLResults.dfBNode) ) { + String label = parser.getElementText() ; + Node node = null ; + // if ( inputGraphLabels.getValue() ) + if ( inputGraphLabels ) + node = NodeFactory.createBlankNode(label) ; + else + node = bNodes.get(null, label); + addBinding(binding, Var.alloc(varName), node) ; + break ; + } + + if ( isTag(tag, XMLResults.dfLiteral) ) { + String datatype = parser.getAttributeValue(null, XMLResults.dfAttrDatatype) ; + + // String langTag = parser.getAttributeValue(null, + // "lang") ; + + // Woodstox needs XML_NS despite the javadoc of StAX + // "If the namespaceURI is null the namespace is not checked for equality" + // StAX(.codehaus.org) copes both ways round + String langTag = parser.getAttributeValue(XML_NS, "lang") ; + + // Works for XML literals (returning them as a + // string) + String text = parser.getElementText() ; + + RDFDatatype dType = null ; + if ( datatype != null ) + dType = TypeMapper.getInstance().getSafeTypeByName(datatype) ; + + Node n = NodeFactory.createLiteral(text, langTag, dType) ; + if ( varName == null ) + throw new ResultSetException("No name for variable") ; + addBinding(binding, Var.alloc(varName), n) ; + break ; + } + + if ( isTag(tag, XMLResults.dfUnbound) ) { + break ; + } + if ( isTag(tag, XMLResults.dfURI) ) { + String uri = parser.getElementText() ; + Node node = NodeFactory.createURI(uri) ; + addBinding(binding, Var.alloc(varName), node) ; + break ; + } + break ; + default : + } + } + staxError("getOneSolution: Hit end unexpectedly") ; + return null ; + } + + static protected void addBinding(BindingMap binding, Var var, Node value) { + Node n = binding.get(var); + if ( n != null ) { + // Same - silently skip. + if ( n.equals(value) ) + return; + Log.warn(ResultsStAX.class, + String.format("Multiple occurences of a binding for variable '%s' with different values - ignored", var.getName())); + return; + } + binding.add(var, value); + } + + private boolean isTag(String localName, String expectedName) { + if ( !parser.getNamespaceURI().equals(XMLResults.baseNamespace) ) + return false ; + return localName.equals(expectedName) ; + } + + private void staxError(String msg) { + Log.warn(this, "StAX error: " + msg) ; + throw new ResultSetException(msg) ; + } + + private void staxError(String msg, Throwable th) { + Log.warn(this, "StAX error: " + msg, th) ; + throw new ResultSetException(msg, th) ; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/716b86cf/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsWriter.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsWriter.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsWriter.java new file mode 100644 index 0000000..d1c4c81 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsWriter.java @@ -0,0 +1,113 @@ +/* + * 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.riot.resultset.rw; + +import java.io.OutputStream; + +import org.apache.jena.atlas.lib.NotImplemented; +import org.apache.jena.query.ResultSet; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RiotException; +import org.apache.jena.riot.resultset.ResultSetWriter; +import org.apache.jena.riot.resultset.ResultSetWriterFactory; +import org.apache.jena.riot.resultset.ResultSetWriterRegistry; +import org.apache.jena.sparql.util.Context; + +public class ResultsWriter { + + /** Create a {@code ResultsWriter.Builder}. */ + public static Builder create() { return new Builder() ; } + + public static class Builder { + private Lang lang = null; + private Context context = null; + + /** Provide a {@link Lang} for the writer.*/ + public Builder lang(Lang lang) { + this.lang = lang; + return this; + } + + /** Set the {@link Context}. This defaults to the global settings of {@code ARQ.getContext()}. */ + public Builder context(Context context) { + if ( context != null ) + context = context.copy(); + this.context = context; + return this; + } + + /** Build a {@code ResultWriter} */ + public ResultsWriter build() { + return new ResultsWriter(lang, context); + } + + /** Short form equivalent to {@code build().write(url, ResultSet)} */ + public void write(String url, ResultSet resultSet) { + build().write(url, resultSet); + } + + /** Short form equivalent to {@code build().write(OutputStream, ResultSet)} */ + public void write(OutputStream output, ResultSet resultSet) { + build().write(output, resultSet); + } + } + + private final Lang lang; + private final Context context; + + private ResultsWriter(Lang lang, Context context) { + super(); + this.lang = lang; + this.context = context; + } + + public void write(String url, ResultSet resultSet) { + throw new NotImplemented(); + } + + public void write(OutputStream output, ResultSet resultSet) { + write(output, resultSet, null, lang); + } + + public void write(String url, boolean booleanResult) { + throw new NotImplemented(); + } + + public void write(OutputStream output, boolean booleanResult) { + write(output, null, booleanResult, lang); + } + + private void write(OutputStream output, ResultSet resultSet, Boolean result, Lang lang) { + if ( resultSet == null && result == null ) + throw new RiotException("No result set and no boolean result"); + if ( resultSet != null && result != null ) + throw new RiotException("Both result set and boolean result supplied"); + if ( ! ResultSetWriterRegistry.isRegistered(lang) ) + throw new RiotException("Not registered as a SPARQL result set output syntax: "+lang); + + ResultSetWriterFactory factory = ResultSetWriterRegistry.getFactory(lang); + if ( factory == null ) + throw new RiotException("No ResultSetReaderFactory for "+lang); + ResultSetWriter writer = factory.create(lang); + if ( resultSet != null ) + writer.write(output, resultSet, context); + else + writer.write(output, result, context); + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/716b86cf/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/XMLResults.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/XMLResults.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/XMLResults.java new file mode 100644 index 0000000..ae0c423 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/XMLResults.java @@ -0,0 +1,50 @@ +/* + * 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.riot.resultset.rw; + +import org.apache.jena.sparql.ARQConstants ; + + +public interface XMLResults +{ + public static final int INDENT = 2 ; + + public static final String baseNamespace = ARQConstants.srxPrefix ; + public static final String xsBaseURI = ARQConstants.XML_SCHEMA_NS ; + + public static final String dfAttrVarName = "name" ; + public static final String dfAttrDatatype = "datatype" ; + + public static final String dfNamespace = baseNamespace ; + public static final String dfRootTag = "sparql" ; + public static final String dfHead = "head" ; + public static final String dfVariable = "variable" ; + public static final String dfLink = "link" ; + public static final String dfResults = "results" ; + public static final String dfSolution = "result" ; + public static final String dfBinding = "binding" ; + + public static final String dfBNode = "bnode" ; + public static final String dfURI = "uri" ; + public static final String dfLiteral = "literal" ; + + public static final String dfUnbound = "unbound" ; + + public static final String dfBoolean = "boolean" ; +} http://git-wip-us.apache.org/repos/asf/jena/blob/716b86cf/jena-arq/src/main/java/org/apache/jena/riot/system/stream/StreamManager.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/riot/system/stream/StreamManager.java b/jena-arq/src/main/java/org/apache/jena/riot/system/stream/StreamManager.java index d8ba825..6ba919f 100644 --- a/jena-arq/src/main/java/org/apache/jena/riot/system/stream/StreamManager.java +++ b/jena-arq/src/main/java/org/apache/jena/riot/system/stream/StreamManager.java @@ -86,7 +86,7 @@ public class StreamManager { /** * Return the {@code StreamManager} in a context, or the global one if the context is - * null or does not contain an entry for a {@code StreamManager}. + * null or does not contain a valid entry for a {@code StreamManager}. *

* The {@code StreamManager} is keyed in the context by * {@link SysRIOT#sysStreamManager}. @@ -95,7 +95,8 @@ public class StreamManager { if ( context == null ) return get(); try { - return (StreamManager)context.get(SysRIOT.sysStreamManager, context); + if ( context.isDefined(SysRIOT.sysStreamManager)) + return (StreamManager)context.get(SysRIOT.sysStreamManager); } catch (ClassCastException ex) { log.warn("Context symbol '" + SysRIOT.sysStreamManager + "' is not a " + Lib.classShortName(StreamManager.class));