All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.rdf4j.query.resultio.sparqlxml.AbstractSPARQLXMLWriter Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *******************************************************************************/
package org.eclipse.rdf4j.query.resultio.sparqlxml;

import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.BINDING_NAME_ATT;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.BINDING_TAG;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.BNODE_TAG;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.BOOLEAN_FALSE;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.BOOLEAN_TAG;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.BOOLEAN_TRUE;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.HEAD_TAG;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.HREF_ATT;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.LINK_TAG;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.LITERAL_DATATYPE_ATT;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.LITERAL_LANG_ATT;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.LITERAL_TAG;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.NAMESPACE;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.QNAME;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.RESULT_SET_TAG;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.RESULT_TAG;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.ROOT_TAG;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.URI_TAG;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.VAR_NAME_ATT;
import static org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLConstants.VAR_TAG;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.rdf4j.common.xml.XMLWriter;
import org.eclipse.rdf4j.model.BNode;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.util.Literals;
import org.eclipse.rdf4j.model.vocabulary.SESAMEQNAME;
import org.eclipse.rdf4j.model.vocabulary.XMLSchema;
import org.eclipse.rdf4j.query.Binding;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.QueryResultHandlerException;
import org.eclipse.rdf4j.query.TupleQueryResultHandlerException;
import org.eclipse.rdf4j.query.resultio.AbstractQueryResultWriter;
import org.eclipse.rdf4j.query.resultio.BasicQueryWriterSettings;
import org.eclipse.rdf4j.query.resultio.QueryResultWriter;
import org.eclipse.rdf4j.rio.RioSetting;
import org.eclipse.rdf4j.rio.helpers.BasicWriterSettings;
import org.eclipse.rdf4j.rio.helpers.XMLWriterSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An abstract class to implement the base functionality for both SPARQLBooleanXMLWriter and
 * SPARQLResultsXMLWriter.
 * 
 * @author Peter Ansell
 */
abstract class AbstractSPARQLXMLWriter extends AbstractQueryResultWriter implements QueryResultWriter {

	/*-----------*
	 * Variables *
	 *-----------*/

	/**
	 * XMLWriter to write XML to.
	 */
	protected XMLWriter xmlWriter;

	protected boolean documentOpen = false;

	protected boolean headerOpen = false;

	protected boolean headerComplete = false;

	protected boolean tupleVariablesFound = false;

	/**
	 * Map with keys as namespace URI strings and the values as the shortened prefixes.
	 */
	private Map namespaceTable = new HashMap();

	private final Logger log = LoggerFactory.getLogger(this.getClass());

	/*--------------*
	 * Constructors *
	 *--------------*/

	public AbstractSPARQLXMLWriter(OutputStream out) {
		this(new XMLWriter(out));
	}

	public AbstractSPARQLXMLWriter(XMLWriter xmlWriter) {
		this.xmlWriter = xmlWriter;
		this.xmlWriter.setPrettyPrint(true);
	}

	/*---------*
	 * Methods *
	 *---------*/

	/**
	 * Enables/disables addition of indentation characters and newlines in the XML document. By default,
	 * pretty-printing is set to true. If set to false, no indentation and newlines are
	 * added to the XML document. This method has to be used before writing starts (that is, before
	 * {@link #startDocument} is called).
	 * 
	 * @deprecated Use {@link #getWriterConfig()} .set(BasicWriterSettings.PRETTY_PRINT, prettyPrint) instead.
	 */
	@Deprecated
	public void setPrettyPrint(boolean prettyPrint) {
		getWriterConfig().set(BasicWriterSettings.PRETTY_PRINT, prettyPrint);
		xmlWriter.setPrettyPrint(prettyPrint);
	}

	protected void endDocument()
		throws IOException
	{
		xmlWriter.endTag(ROOT_TAG);

		xmlWriter.endDocument();

		tupleVariablesFound = false;
		headerOpen = false;
		headerComplete = false;
		documentOpen = false;
	}

	@Override
	public void handleBoolean(boolean value)
		throws QueryResultHandlerException
	{
		if (!documentOpen) {
			startDocument();
		}

		if (!headerOpen) {
			startHeader();
		}

		if (!headerComplete) {
			endHeader();
		}

		if (tupleVariablesFound) {
			throw new QueryResultHandlerException("Cannot call handleBoolean after startQueryResults");
		}

		try {
			if (value) {
				xmlWriter.textElement(BOOLEAN_TAG, BOOLEAN_TRUE);
			}
			else {
				xmlWriter.textElement(BOOLEAN_TAG, BOOLEAN_FALSE);
			}

			endDocument();
		}
		catch (IOException e) {
			throw new QueryResultHandlerException(e);
		}
	}

	@Override
	public void startDocument()
		throws QueryResultHandlerException
	{
		if (!documentOpen) {
			documentOpen = true;
			headerOpen = false;
			headerComplete = false;
			tupleVariablesFound = false;

			try {
				xmlWriter.setPrettyPrint(getWriterConfig().get(BasicWriterSettings.PRETTY_PRINT));

				if (getWriterConfig().get(XMLWriterSettings.INCLUDE_XML_PI)) {
					xmlWriter.startDocument();
				}

				xmlWriter.setAttribute("xmlns", NAMESPACE);

				if (getWriterConfig().get(BasicQueryWriterSettings.ADD_SESAME_QNAME)) {
					xmlWriter.setAttribute("xmlns:q", SESAMEQNAME.NAMESPACE);
				}

				for (String nextPrefix : namespaceTable.keySet()) {
					this.log.debug("Adding custom prefix for <{}> to map to <{}>", nextPrefix,
							namespaceTable.get(nextPrefix));
					xmlWriter.setAttribute("xmlns:" + namespaceTable.get(nextPrefix), nextPrefix);
				}
			}
			catch (IOException e) {
				throw new QueryResultHandlerException(e);
			}
		}
	}

	@Override
	public void handleStylesheet(String url)
		throws QueryResultHandlerException
	{
		if (!documentOpen) {
			startDocument();
		}

		try {
			xmlWriter.writeStylesheet(url);
		}
		catch (IOException e) {
			throw new QueryResultHandlerException(e);
		}
	}

	@Override
	public void startHeader()
		throws QueryResultHandlerException
	{
		if (!documentOpen) {
			startDocument();
		}

		if (!headerOpen) {
			try {
				xmlWriter.startTag(ROOT_TAG);

				xmlWriter.startTag(HEAD_TAG);

				headerOpen = true;
			}
			catch (IOException e) {
				throw new QueryResultHandlerException(e);
			}
		}
	}

	@Override
	public void handleLinks(List linkUrls)
		throws QueryResultHandlerException
	{
		if (!documentOpen) {
			startDocument();
		}

		if (!headerOpen) {
			startHeader();
		}

		try {
			// Write link URLs
			for (String name : linkUrls) {
				xmlWriter.setAttribute(HREF_ATT, name);
				xmlWriter.emptyElement(LINK_TAG);
			}
		}
		catch (IOException e) {
			throw new QueryResultHandlerException(e);
		}
	}

	@Override
	public void endHeader()
		throws QueryResultHandlerException
	{
		if (!documentOpen) {
			startDocument();
		}

		if (!headerOpen) {
			startHeader();
		}

		if (!headerComplete) {
			try {
				xmlWriter.endTag(HEAD_TAG);

				if (tupleVariablesFound) {
					// Write start of results, which must always exist, even if there
					// are no result bindings
					xmlWriter.startTag(RESULT_SET_TAG);
				}

				headerComplete = true;
			}
			catch (IOException e) {
				throw new QueryResultHandlerException(e);
			}
		}
	}

	@Override
	public void startQueryResult(List bindingNames)
		throws TupleQueryResultHandlerException
	{
		try {
			if (!documentOpen) {
				startDocument();
			}
			if (!headerOpen) {
				startHeader();
			}

			tupleVariablesFound = true;
			// Write binding names
			for (String name : bindingNames) {
				xmlWriter.setAttribute(VAR_NAME_ATT, name);
				xmlWriter.emptyElement(VAR_TAG);
			}
		}
		catch (IOException e) {
			throw new TupleQueryResultHandlerException(e);
		}
		catch (TupleQueryResultHandlerException e) {
			throw e;
		}
		catch (QueryResultHandlerException e) {
			throw new TupleQueryResultHandlerException(e);
		}
	}

	@Override
	public void endQueryResult()
		throws TupleQueryResultHandlerException
	{
		try {
			if (!documentOpen) {
				startDocument();
			}

			if (!headerOpen) {
				startHeader();
			}

			if (!headerComplete) {
				endHeader();
			}

			if (!tupleVariablesFound) {
				throw new IllegalStateException(
						"Could not end query result as startQueryResult was not called first.");
			}

			xmlWriter.endTag(RESULT_SET_TAG);
			endDocument();
		}
		catch (IOException e) {
			throw new TupleQueryResultHandlerException(e);
		}
		catch (TupleQueryResultHandlerException e) {
			throw e;
		}
		catch (QueryResultHandlerException e) {
			throw new TupleQueryResultHandlerException(e);
		}
	}

	@Override
	public void handleSolution(BindingSet bindingSet)
		throws TupleQueryResultHandlerException
	{
		try {
			if (!documentOpen) {
				startDocument();
			}

			if (!headerOpen) {
				startHeader();
			}

			if (!headerComplete) {
				endHeader();
			}

			if (!tupleVariablesFound) {
				throw new IllegalStateException("Must call startQueryResult before handleSolution");
			}

			xmlWriter.startTag(RESULT_TAG);

			for (Binding binding : bindingSet) {
				xmlWriter.setAttribute(BINDING_NAME_ATT, binding.getName());
				xmlWriter.startTag(BINDING_TAG);

				writeValue(binding.getValue());

				xmlWriter.endTag(BINDING_TAG);
			}

			xmlWriter.endTag(RESULT_TAG);
		}
		catch (IOException e) {
			throw new TupleQueryResultHandlerException(e);
		}
		catch (TupleQueryResultHandlerException e) {
			throw e;
		}
		catch (QueryResultHandlerException e) {
			throw new TupleQueryResultHandlerException(e);
		}
	}

	@Override
	public final Collection> getSupportedSettings() {
		Set> result = new HashSet>(super.getSupportedSettings());

		result.add(BasicWriterSettings.PRETTY_PRINT);
		result.add(BasicQueryWriterSettings.ADD_SESAME_QNAME);
		result.add(BasicWriterSettings.XSD_STRING_TO_PLAIN_LITERAL);

		return result;
	}

	@Override
	public void handleNamespace(String prefix, String uri)
		throws QueryResultHandlerException
	{
		// we only support the addition of prefixes before the document is open
		// fail silently if namespaces are added after this point
		if (!documentOpen) {
			// SES-1751 : Do not allow overriding of the fixed sparql or
			// sesameqname prefixes
			if (!prefix.trim().isEmpty() && !prefix.trim().equals(SESAMEQNAME.PREFIX)) {
				this.log.debug("Handle namespace: Will map <{}> to <{}>", uri, prefix);
				// NOTE: The keys in the namespace table are the URIs and the values
				// are the prefixes
				this.namespaceTable.put(uri, prefix);
			}
			else {
				this.log.debug(
						"handleNamespace was ignored for either the empty prefix or the sesame qname prefix (q). Attempted to map: <{}> to <{}>",
						uri, prefix);
			}
		}
		else {
			this.log.warn("handleNamespace was ignored after startDocument: <{}> to <{}>", uri, prefix);
		}
	}

	private void writeValue(Value value)
		throws IOException
	{
		if (value instanceof IRI) {
			writeURI((IRI)value);
		}
		else if (value instanceof BNode) {
			writeBNode((BNode)value);
		}
		else if (value instanceof Literal) {
			writeLiteral((Literal)value);
		}
	}

	private boolean isQName(IRI nextUri) {
		return namespaceTable.containsKey(nextUri.getNamespace());
	}

	/**
	 * Write a QName for the given URI if and only if the {@link BasicQueryWriterSettings#ADD_SESAME_QNAME}
	 * setting has been set to true. By default it is false, to ensure that this implementation stays within
	 * the specification by default.
	 * 
	 * @param nextUri
	 *        The prefixed URI to be written as a sesame qname attribute.
	 */
	private void writeQName(IRI nextUri) {
		if (getWriterConfig().get(BasicQueryWriterSettings.ADD_SESAME_QNAME)) {
			xmlWriter.setAttribute(QNAME,
					namespaceTable.get(nextUri.getNamespace()) + ":" + nextUri.getLocalName());
		}
	}

	private void writeURI(IRI uri)
		throws IOException
	{
		if (isQName(uri)) {
			writeQName(uri);
		}
		xmlWriter.textElement(URI_TAG, uri.toString());
	}

	private void writeBNode(BNode bNode)
		throws IOException
	{
		xmlWriter.textElement(BNODE_TAG, bNode.getID());
	}

	private void writeLiteral(Literal literal)
		throws IOException
	{
		if (Literals.isLanguageLiteral(literal)) {
			xmlWriter.setAttribute(LITERAL_LANG_ATT, literal.getLanguage().get());
		}
		// Only enter this section for non-language literals now, as the
		// rdf:langString datatype is handled implicitly above
		else {
			IRI datatype = literal.getDatatype();
			boolean ignoreDatatype = datatype.equals(XMLSchema.STRING) && xsdStringToPlainLiteral();
			if (!ignoreDatatype) {
				if (isQName(datatype)) {
					writeQName(datatype);
				}
				xmlWriter.setAttribute(LITERAL_DATATYPE_ATT, datatype.stringValue());
			}
		}

		xmlWriter.textElement(LITERAL_TAG, literal.getLabel());
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy