/*
* 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 java.io.InputStream ;
import java.io.Reader ;
import java.io.StringReader ;
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.lib.StrUtils ;
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.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.util.LabelToNodeMap ;
/**
* Code that reads an XML Results format and builds the ARQ structure for the
* same. Can read result set and boolean result forms. This is a streaming
* implementation.
*/
class XMLInputStAX extends SPARQLResult {
private static final String XML_NS = ARQConstants.XML_NS ;
public static ResultSet fromXML(InputStream in) {
return fromXML(in, null) ;
}
public static ResultSet fromXML(InputStream in, Model model) {
XMLInputStAX x = new XMLInputStAX(in, model) ;
if ( !x.isResultSet() )
throw new ResultSetException("Not a result set") ;
return x.getResultSet() ;
}
public static ResultSet fromXML(String str) {
return fromXML(str, null) ;
}
public static ResultSet fromXML(String str, Model model) {
XMLInputStAX x = new XMLInputStAX(str, model) ;
if ( !x.isResultSet() )
throw new ResultSetException("Not a result set") ;
return x.getResultSet() ;
}
public static boolean booleanFromXML(InputStream in) {
XMLInputStAX x = new XMLInputStAX(in) ;
return x.getBooleanResult() ;
}
public static boolean booleanFromXML(String str) {
XMLInputStAX x = new XMLInputStAX(str) ;
return x.getBooleanResult() ;
}
public XMLInputStAX(InputStream in) {
this(in, null) ;
}
public XMLInputStAX(InputStream in, Model model) {
XMLInputFactory xf = XMLInputFactory.newInstance() ;
try {
XMLStreamReader xReader = xf.createXMLStreamReader(in) ;
worker(xReader, model) ;
} 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 XMLInputStAX(Reader in, Model model) {
XMLInputFactory xf = XMLInputFactory.newInstance() ;
try {
XMLStreamReader xReader = xf.createXMLStreamReader(in) ;
worker(xReader, model) ;
} 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 XMLInputStAX(String str) {
this(str, null) ;
}
public XMLInputStAX(String str, Model model) {
XMLInputFactory xf = XMLInputFactory.newInstance() ;
try {
Reader r = new StringReader(str) ;
XMLStreamReader xReader = xf.createXMLStreamReader(r) ;
worker(xReader, model) ;
} 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 void worker(XMLStreamReader xReader, Model model) {
if ( model == null )
model = GraphFactory.makeJenaDefaultModel() ;
ResultSetStAX rss = new ResultSetStAX(xReader, model) ;
if ( rss.isResultSet )
set(rss) ;
else
set(rss.askResult) ;
}
// private XMLInputStAX()
// -------- Result Set
static class ResultSetStAX implements ResultSet, Closeable {
// ResultSet variables
QuerySolution current = null ;
XMLStreamReader parser = null ;
List<String> variables = new ArrayList<>() ;
Binding binding = null ; // Current
// binding
// RefBoolean inputGraphLabels = new
// RefBoolean(ARQ.inputGraphBNodeLabels, false) ;
boolean inputGraphLabels = ARQ.isTrue(ARQ.inputGraphBNodeLabels) ;
LabelToNodeMap bNodes = LabelToNodeMap.createBNodeMap() ;
// Type
boolean isResultSet = false ;
// Result set
boolean ordered = false ;
boolean distinct = false ;
boolean finished = false ;
Model model = null ;
int row = 0 ;
// boolean
boolean askResult = false ;
ResultSetStAX(XMLStreamReader reader, Model model) {
parser = reader ;
this.model = model ;
init() ;
}
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 <result>, <boolean> element or </results>
// 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<String> 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(XMLInputStAX.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 <boolean>
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 = StrUtils.strjoin(", ", startElementNames) ;
String s2 = "" ;
if ( stopElementNames != null )
s2 = StrUtils.strjoin(", ", 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 ;
}
private Binding getOneSolution() throws XMLStreamException {
if ( finished )
return null ;
// At the start of <result>
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.asNode(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 ;
}
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) ;
}
}
}