/* * 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.sparql.resultset; import static org.apache.jena.sparql.resultset.JSONResultsKW.*; import java.io.OutputStream; import java.util.HashMap; import java.util.Iterator; import java.util.Map; 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; /** * A JSON writer for SPARQL Result Sets. Uses Jena Atlas JSON support. * * Format: <a href="http://www.w3.org/TR/sparql11-results-json/">SPARQL 1.1 * Query Results JSON Format</a> */ public class JSONOutputResultSet implements ResultSetProcessor { static boolean multiLineValues = false; static boolean multiLineVarNames = false; private boolean outputGraphBNodeLabels = false; private IndentedWriter out; private int bNodeCounter = 0; private Map<Resource, String> bNodeMap = new HashMap<>(); JSONOutputResultSet(OutputStream outStream) { this(new IndentedWriter(outStream)); } JSONOutputResultSet(IndentedWriter indentedOut) { out = indentedOut; outputGraphBNodeLabels = ARQ.isTrue(ARQ.outputGraphBNodeLabels); } @Override public void start(ResultSet rs) { out.println("{"); out.incIndent(); doHead(rs); out.println(quoteName(kResults) + ": {"); out.incIndent(); out.println(quoteName(kBindings) + ": ["); out.incIndent(); firstSolution = true; } @Override public void finish(ResultSet rs) { // Close last binding. out.println(); out.decIndent(); // bindings out.println("]"); out.decIndent(); out.println("}"); // results out.decIndent(); out.println("}"); // top level {} out.flush(); } private void doHead(ResultSet rs) { out.println(quoteName(kHead) + ": {"); out.incIndent(); doLink(rs); doVars(rs); out.decIndent(); out.println("} ,"); } private void doLink(ResultSet rs) { // ---- link // out.println("\"link\": []") ; } private void doVars(ResultSet rs) { // On one line. out.print(quoteName(kVars) + ": [ "); if ( multiLineVarNames ) out.println(); out.incIndent(); for ( Iterator<String> iter = rs.getResultVars().iterator() ; iter.hasNext() ; ) { String varname = iter.next(); out.print("\"" + varname + "\""); if ( multiLineVarNames ) out.println(); if ( iter.hasNext() ) out.print(" , "); } out.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 ) out.println(" ,"); firstSolution = false; out.println("{"); out.incIndent(); firstBindingInSolution = true; } @Override public void finish(QuerySolution qs) { out.println(); // Finish last binding out.decIndent(); out.print("}"); // NB No newline } @Override public void binding(String varName, RDFNode value) { if ( value == null ) return; if ( !firstBindingInSolution ) out.println(" ,"); firstBindingInSolution = false; // Do not use quoteName - varName may not be JSON-safe as a bare name. out.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 ) out.print(" "); out.print("}"); // NB No newline } // private void printUnbound() // { // out.print(quoteName(kType)+ ": "+quote(kUnbound)+" , ") ; // if ( multiLineValues ) out.println() ; // out.print(quoteName(kValue)+": null") ; // if ( multiLineValues ) out.println() ; // } private void printLiteral(Literal literal) { String datatype = literal.getDatatypeURI(); String lang = literal.getLanguage(); if ( Util.isSimpleString(literal) || Util.isLangString(literal) ) { out.print(quoteName(kType) + ": " + quote(kLiteral) + " , "); if ( multiLineValues ) out.println(); if ( lang != null && !lang.equals("") ) { out.print(quoteName(kXmlLang) + ": " + quote(lang) + " , "); if ( multiLineValues ) out.println(); } } else { out.print(quoteName(kType) + ": " + quote(kLiteral) + " , "); if ( multiLineValues ) out.println(); out.print(quoteName(kDatatype) + ": " + quote(datatype) + " , "); if ( multiLineValues ) out.println(); } out.print(quoteName(kValue) + ": " + quote(literal.getLexicalForm())); if ( multiLineValues ) out.println(); } private void printResource(Resource resource) { if ( resource.isAnon() ) { String label; if ( outputGraphBNodeLabels ) label = resource.getId().getLabelString(); else { if ( !bNodeMap.containsKey(resource) ) bNodeMap.put(resource, "b" + (bNodeCounter++)); label = bNodeMap.get(resource); } out.print(quoteName(kType) + ": " + quote(kBnode) + " , "); if ( multiLineValues ) out.println(); out.print(quoteName(kValue) + ": " + quote(label)); if ( multiLineValues ) out.println(); } else { out.print(quoteName(kType) + ": " + quote(kUri) + " , "); if ( multiLineValues ) out.println(); out.print(quoteName(kValue) + ": " + quote(resource.getURI())); if ( multiLineValues ) out.println(); return; } } private static String quote(String string) { 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) { // Safest to quote anyway. return quote(string); // Assumes only called with safe names // return string ; // Better would be: // starts a-z, constains a-z,0-9, not a keyword(true, false, null) // if ( string.contains(something not in a-z0-9) // and // //return "\""+string+"\"" ; // return JSONObject.quote(string) ; } }