com.mysql.jdbc.JDBC4MysqlSQLXML Maven / Gradle / Ivy
/*
Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.
The MySQL Connector/J is licensed under the terms of the GPLv2
, like most MySQL Connectors.
There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
this software, see the FLOSS License Exception
.
This program is free software; you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation; version 2
of the License.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this
program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
Floor, Boston, MA 02110-1301 USA
*/
package com.mysql.jdbc;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLXML;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stax.StAXResult;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.SAXException;
public class JDBC4MysqlSQLXML implements SQLXML {
private XMLInputFactory inputFactory;
private XMLOutputFactory outputFactory;
private String stringRep;
private ResultSetInternalMethods owningResultSet;
private int columnIndexOfXml;
private boolean fromResultSet;
private boolean isClosed = false;
private boolean workingWithResult;
private DOMResult asDOMResult;
private SAXResult asSAXResult;
private SimpleSaxToReader saxToReaderConverter;
private StringWriter asStringWriter;
private ByteArrayOutputStream asByteArrayOutputStream;
private ExceptionInterceptor exceptionInterceptor;
protected JDBC4MysqlSQLXML(ResultSetInternalMethods owner, int index, ExceptionInterceptor exceptionInterceptor) {
this.owningResultSet = owner;
this.columnIndexOfXml = index;
this.fromResultSet = true;
this.exceptionInterceptor = exceptionInterceptor;
}
protected JDBC4MysqlSQLXML(ExceptionInterceptor exceptionInterceptor) {
this.fromResultSet = false;
this.exceptionInterceptor = exceptionInterceptor;
}
public synchronized void free() throws SQLException {
this.stringRep = null;
this.asDOMResult = null;
this.asSAXResult = null;
this.inputFactory = null;
this.outputFactory = null;
this.owningResultSet = null;
this.workingWithResult = false;
this.isClosed = true;
}
public synchronized String getString() throws SQLException {
checkClosed();
checkWorkingWithResult();
if (this.fromResultSet) {
return this.owningResultSet.getString(this.columnIndexOfXml);
}
return this.stringRep;
}
private synchronized void checkClosed() throws SQLException {
if (this.isClosed) {
throw SQLError.createSQLException("SQLXMLInstance has been free()d", this.exceptionInterceptor);
}
}
private synchronized void checkWorkingWithResult() throws SQLException {
if (this.workingWithResult) {
throw SQLError.createSQLException("Can't perform requested operation after getResult() has been called to write XML data",
SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
}
}
/**
* Sets the XML value designated by this SQLXML instance to the given String
* representation. The format of this String is defined by
* org.xml.sax.InputSource, where the characters in the stream represent the
* unicode code points for XML according to section 2 and appendix B of the
* XML 1.0 specification. Although an encoding declaration other than
* unicode may be present, the encoding of the String is unicode. The
* behavior of this method is the same as ResultSet.updateString() when the
* designated column of the ResultSet has a type java.sql.Types of SQLXML.
*
* The SQL XML object becomes not writeable when this method is called and may also become not readable depending on implementation.
*
* @param value
* the XML value
* @throws SQLException
* if there is an error processing the XML value. The getCause()
* method of the exception may provide a more detailed
* exception, for example, if the stream does not contain valid
* characters. An exception is thrown if the state is not
* writable.
* @exception SQLFeatureNotSupportedException
* if the JDBC driver does not support this method
* @since 1.6
*/
public synchronized void setString(String str) throws SQLException {
checkClosed();
checkWorkingWithResult();
this.stringRep = str;
this.fromResultSet = false;
}
public synchronized boolean isEmpty() throws SQLException {
checkClosed();
checkWorkingWithResult();
if (!this.fromResultSet) {
return this.stringRep == null || this.stringRep.length() == 0;
}
return false;
}
public synchronized InputStream getBinaryStream() throws SQLException {
checkClosed();
checkWorkingWithResult();
return this.owningResultSet.getBinaryStream(this.columnIndexOfXml);
}
/**
* Retrieves the XML value designated by this SQLXML instance as a
* java.io.Reader object. The format of this stream is defined by
* org.xml.sax.InputSource, where the characters in the stream represent the
* unicode code points for XML according to section 2 and appendix B of the
* XML 1.0 specification. Although an encoding declaration other than
* unicode may be present, the encoding of the stream is unicode. The
* behavior of this method is the same as ResultSet.getCharacterStream()
* when the designated column of the ResultSet has a type java.sql.Types of
* SQLXML.
*
* The SQL XML object becomes not readable when this method is called and may also become not writable depending on implementation.
*
* @return a stream containing the XML data.
* @throws SQLException
* if there is an error processing the XML value. The getCause()
* method of the exception may provide a more detailed
* exception, for example, if the stream does not contain valid
* characters. An exception is thrown if the state is not
* readable.
* @exception SQLFeatureNotSupportedException
* if the JDBC driver does not support this method
* @since 1.6
*/
public synchronized Reader getCharacterStream() throws SQLException {
checkClosed();
checkWorkingWithResult();
return this.owningResultSet.getCharacterStream(this.columnIndexOfXml);
}
/**
* Returns a Source for reading the XML value designated by this SQLXML
* instance. Sources are used as inputs to XML parsers and XSLT
* transformers.
*
* Sources for XML parsers will have namespace processing on by default. The systemID of the Source is implementation dependent.
*
* The SQL XML object becomes not readable when this method is called and may also become not writable depending on implementation.
*
* Note that SAX is a callback architecture, so a returned SAXSource should then be set with a content handler that will receive the SAX events from
* parsing. The content handler will receive callbacks based on the contents of the XML.
*
*
* SAXSource saxSource = sqlxml.getSource(SAXSource.class);
* XMLReader xmlReader = saxSource.getXMLReader();
* xmlReader.setContentHandler(myHandler);
* xmlReader.parse(saxSource.getInputSource());
*
*
* @param sourceClass
* The class of the source, or null. If the class is null, a
* vendor specifc Source implementation will be returned. The
* following classes are supported at a minimum:
*
* (MySQL returns a SAXSource if sourceClass == null)
*
*
* javax.xml.transform.dom.DOMSource - returns a DOMSource
* javax.xml.transform.sax.SAXSource - returns a SAXSource
* javax.xml.transform.stax.StAXSource - returns a StAXSource
* javax.xml.transform.stream.StreamSource - returns a StreamSource
*
*
* @return a Source for reading the XML value.
* @throws SQLException
* if there is an error processing the XML value or if this
* feature is not supported. The getCause() method of the
* exception may provide a more detailed exception, for example,
* if an XML parser exception occurs. An exception is thrown if
* the state is not readable.
* @exception SQLFeatureNotSupportedException
* if the JDBC driver does not support this method
* @since 1.6
*/
public synchronized Source getSource(Class clazz) throws SQLException {
checkClosed();
checkWorkingWithResult();
// Note that we try and use streams here wherever possible for the day that the server actually supports streaming from server -> client
// (futureproofing)
if (clazz == null || clazz.equals(SAXSource.class)) {
InputSource inputSource = null;
if (this.fromResultSet) {
inputSource = new InputSource(this.owningResultSet.getCharacterStream(this.columnIndexOfXml));
} else {
inputSource = new InputSource(new StringReader(this.stringRep));
}
return new SAXSource(inputSource);
} else if (clazz.equals(DOMSource.class)) {
try {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(true);
DocumentBuilder builder = builderFactory.newDocumentBuilder();
InputSource inputSource = null;
if (this.fromResultSet) {
inputSource = new InputSource(this.owningResultSet.getCharacterStream(this.columnIndexOfXml));
} else {
inputSource = new InputSource(new StringReader(this.stringRep));
}
return new DOMSource(builder.parse(inputSource));
} catch (Throwable t) {
SQLException sqlEx = SQLError.createSQLException(t.getMessage(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
sqlEx.initCause(t);
throw sqlEx;
}
} else if (clazz.equals(StreamSource.class)) {
Reader reader = null;
if (this.fromResultSet) {
reader = this.owningResultSet.getCharacterStream(this.columnIndexOfXml);
} else {
reader = new StringReader(this.stringRep);
}
return new StreamSource(reader);
} else if (clazz.equals(StAXSource.class)) {
try {
Reader reader = null;
if (this.fromResultSet) {
reader = this.owningResultSet.getCharacterStream(this.columnIndexOfXml);
} else {
reader = new StringReader(this.stringRep);
}
return new StAXSource(this.inputFactory.createXMLStreamReader(reader));
} catch (XMLStreamException ex) {
SQLException sqlEx = SQLError.createSQLException(ex.getMessage(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
sqlEx.initCause(ex);
throw sqlEx;
}
} else {
throw SQLError.createSQLException("XML Source of type \"" + clazz.toString() + "\" Not supported.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
this.exceptionInterceptor);
}
}
/**
* Retrieves a stream that can be used to write the XML value that this
* SQLXML instance represents. The stream begins at position 0. The bytes of
* the stream are interpreted according to appendix F of the XML 1.0
* specification The behavior of this method is the same as
* ResultSet.updateBinaryStream() when the designated column of the
* ResultSet has a type java.sql.Types of SQLXML.
*
* The SQL XML object becomes not writeable when this method is called and may also become not readable depending on implementation.
*
* @return a stream to which data can be written.
* @throws SQLException
* if there is an error processing the XML value. An exception
* is thrown if the state is not writable.
* @exception SQLFeatureNotSupportedException
* if the JDBC driver does not support this method
* @since 1.6
*/
public synchronized OutputStream setBinaryStream() throws SQLException {
checkClosed();
checkWorkingWithResult();
this.workingWithResult = true;
return setBinaryStreamInternal();
}
private synchronized OutputStream setBinaryStreamInternal() throws SQLException {
this.asByteArrayOutputStream = new ByteArrayOutputStream();
return this.asByteArrayOutputStream;
}
/**
* Retrieves a stream to be used to write the XML value that this SQLXML
* instance represents. The format of this stream is defined by
* org.xml.sax.InputSource, where the characters in the stream represent the
* unicode code points for XML according to section 2 and appendix B of the
* XML 1.0 specification. Although an encoding declaration other than
* unicode may be present, the encoding of the stream is unicode. The
* behavior of this method is the same as ResultSet.updateCharacterStream()
* when the designated column of the ResultSet has a type java.sql.Types of
* SQLXML.
*
* The SQL XML object becomes not writeable when this method is called and may also become not readable depending on implementation.
*
* @return a stream to which data can be written.
* @throws SQLException
* if there is an error processing the XML value. The getCause()
* method of the exception may provide a more detailed
* exception, for example, if the stream does not contain valid
* characters. An exception is thrown if the state is not
* writable.
* @exception SQLFeatureNotSupportedException
* if the JDBC driver does not support this method
* @since 1.6
*/
public synchronized Writer setCharacterStream() throws SQLException {
checkClosed();
checkWorkingWithResult();
this.workingWithResult = true;
return setCharacterStreamInternal();
}
private synchronized Writer setCharacterStreamInternal() throws SQLException {
this.asStringWriter = new StringWriter();
return this.asStringWriter;
}
/**
* Returns a Result for setting the XML value designated by this SQLXML
* instance.
*
* The systemID of the Result is implementation dependent.
*
* The SQL XML object becomes not writeable when this method is called and may also become not readable depending on implementation.
*
* Note that SAX is a callback architecture and the returned SAXResult has a content handler assigned that will receive the SAX events based on the contents
* of the XML. Call the content handler with the contents of the XML document to assign the values.
*
*
* SAXResult saxResult = sqlxml.setResult(SAXResult.class);
* ContentHandler contentHandler = saxResult.getXMLReader().getContentHandler();
* contentHandler.startDocument();
* // set the XML elements and attributes into the result
* contentHandler.endDocument();
*
*
* @param resultClass
* The class of the result, or null. If resultClass is null, a
* vendor specific Result implementation will be returned. The
* following classes are supported at a minimum:
*
*
* javax.xml.transform.dom.DOMResult - returns a DOMResult
* javax.xml.transform.sax.SAXResult - returns a SAXResult
* javax.xml.transform.stax.StAXResult - returns a StAXResult
* javax.xml.transform.stream.StreamResult - returns a StreamResult
*
*
* @return Returns a Result for setting the XML value.
* @throws SQLException
* if there is an error processing the XML value or if this
* feature is not supported. The getCause() method of the
* exception may provide a more detailed exception, for example,
* if an XML parser exception occurs. An exception is thrown if
* the state is not writable.
* @exception SQLFeatureNotSupportedException
* if the JDBC driver does not support this method
* @since 1.6
*/
public synchronized Result setResult(Class clazz) throws SQLException {
checkClosed();
checkWorkingWithResult();
this.workingWithResult = true;
this.asDOMResult = null;
this.asSAXResult = null;
this.saxToReaderConverter = null;
this.stringRep = null;
this.asStringWriter = null;
this.asByteArrayOutputStream = null;
if (clazz == null || clazz.equals(SAXResult.class)) {
this.saxToReaderConverter = new SimpleSaxToReader();
this.asSAXResult = new SAXResult(this.saxToReaderConverter);
return this.asSAXResult;
} else if (clazz.equals(DOMResult.class)) {
this.asDOMResult = new DOMResult();
return this.asDOMResult;
} else if (clazz.equals(StreamResult.class)) {
return new StreamResult(setCharacterStreamInternal());
} else if (clazz.equals(StAXResult.class)) {
try {
if (this.outputFactory == null) {
this.outputFactory = XMLOutputFactory.newInstance();
}
return new StAXResult(this.outputFactory.createXMLEventWriter(setCharacterStreamInternal()));
} catch (XMLStreamException ex) {
SQLException sqlEx = SQLError.createSQLException(ex.getMessage(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
sqlEx.initCause(ex);
throw sqlEx;
}
} else {
throw SQLError.createSQLException("XML Result of type \"" + clazz.toString() + "\" Not supported.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
this.exceptionInterceptor);
}
}
private Reader binaryInputStreamStreamToReader(ByteArrayOutputStream out) {
try {
// There's got to be an easier way to do this, but I don't feel like coding up Appendix F of the XML Spec myself, when there's a reusable way to do
// it, and we can warn folks away from BINARY xml streams that have to be parsed to determine the character encoding :P
String encoding = "UTF-8";
try {
ByteArrayInputStream bIn = new ByteArrayInputStream(out.toByteArray());
XMLStreamReader reader = this.inputFactory.createXMLStreamReader(bIn);
int eventType = 0;
while ((eventType = reader.next()) != XMLStreamReader.END_DOCUMENT) {
if (eventType == XMLStreamReader.START_DOCUMENT) {
String possibleEncoding = reader.getEncoding();
if (possibleEncoding != null) {
encoding = possibleEncoding;
}
break;
}
}
} catch (Throwable t) {
// ignore, dealt with later when the string can't be parsed into valid XML
}
return new StringReader(new String(out.toByteArray(), encoding));
} catch (UnsupportedEncodingException badEnc) {
throw new RuntimeException(badEnc);
}
}
protected String readerToString(Reader reader) throws SQLException {
StringBuffer buf = new StringBuffer();
int charsRead = 0;
char[] charBuf = new char[512];
try {
while ((charsRead = reader.read(charBuf)) != -1) {
buf.append(charBuf, 0, charsRead);
}
} catch (IOException ioEx) {
SQLException sqlEx = SQLError.createSQLException(ioEx.getMessage(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
sqlEx.initCause(ioEx);
throw sqlEx;
}
return buf.toString();
}
protected synchronized Reader serializeAsCharacterStream() throws SQLException {
checkClosed();
if (this.workingWithResult) {
// figure out what kind of result
if (this.stringRep != null) {
return new StringReader(this.stringRep);
}
if (this.asDOMResult != null) {
return new StringReader(domSourceToString());
}
if (this.asStringWriter != null) { // stax result
return new StringReader(this.asStringWriter.toString());
}
if (this.asSAXResult != null) {
return this.saxToReaderConverter.toReader();
}
if (this.asByteArrayOutputStream != null) {
return binaryInputStreamStreamToReader(this.asByteArrayOutputStream);
}
}
return this.owningResultSet.getCharacterStream(this.columnIndexOfXml);
}
protected String domSourceToString() throws SQLException {
try {
DOMSource source = new DOMSource(this.asDOMResult.getNode());
Transformer identity = TransformerFactory.newInstance().newTransformer();
StringWriter stringOut = new StringWriter();
Result result = new StreamResult(stringOut);
identity.transform(source, result);
return stringOut.toString();
} catch (Throwable t) {
SQLException sqlEx = SQLError.createSQLException(t.getMessage(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
sqlEx.initCause(t);
throw sqlEx;
}
}
protected synchronized String serializeAsString() throws SQLException {
checkClosed();
if (this.workingWithResult) {
// figure out what kind of result
if (this.stringRep != null) {
return this.stringRep;
}
if (this.asDOMResult != null) {
return domSourceToString();
}
if (this.asStringWriter != null) { // stax result
return this.asStringWriter.toString();
}
if (this.asSAXResult != null) {
return readerToString(this.saxToReaderConverter.toReader());
}
if (this.asByteArrayOutputStream != null) {
return readerToString(binaryInputStreamStreamToReader(this.asByteArrayOutputStream));
}
}
return this.owningResultSet.getString(this.columnIndexOfXml);
}
/*
* The SimpleSaxToReader class is an adaptation of the SAX "Writer"
* example from the Apache XercesJ-2 Project. The license for this
* code is as follows:
*
* 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.
*/
class SimpleSaxToReader extends DefaultHandler {
StringBuffer buf = new StringBuffer();
public void startDocument() throws SAXException {
buf.append("");
}
public void endDocument() throws SAXException {
// Do we need to override this?
}
public void startElement(String namespaceURI, String sName, String qName, Attributes attrs) throws SAXException {
this.buf.append("<");
this.buf.append(qName);
if (attrs != null) {
for (int i = 0; i < attrs.getLength(); i++) {
this.buf.append(" ");
this.buf.append(attrs.getQName(i)).append("=\"");
escapeCharsForXml(attrs.getValue(i), true);
this.buf.append("\"");
}
}
this.buf.append(">");
}
public void characters(char buf[], int offset, int len) throws SAXException {
if (!this.inCDATA) {
escapeCharsForXml(buf, offset, len, false);
} else {
this.buf.append(buf, offset, len);
}
}
public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
characters(ch, start, length);
}
private boolean inCDATA = false;
public void startCDATA() throws SAXException {
this.buf.append("");
}
public void comment(char ch[], int start, int length) throws SAXException {
// if (!fCanonical && fElementDepth > 0) {
this.buf.append("");
// }
}
Reader toReader() {
return new StringReader(this.buf.toString());
}
private void escapeCharsForXml(String str, boolean isAttributeData) {
if (str == null) {
return;
}
int strLen = str.length();
for (int i = 0; i < strLen; i++) {
escapeCharsForXml(str.charAt(i), isAttributeData);
}
}
private void escapeCharsForXml(char[] buf, int offset, int len, boolean isAttributeData) {
if (buf == null) {
return;
}
for (int i = 0; i < len; i++) {
escapeCharsForXml(buf[offset + i], isAttributeData);
}
}
private void escapeCharsForXml(char c, boolean isAttributeData) {
switch (c) {
case '<':
this.buf.append("<");
break;
case '>':
this.buf.append(">");
break;
case '&':
this.buf.append("&");
break;
case '"':
if (!isAttributeData) {
this.buf.append("\"");
} else {
this.buf.append(""");
}
break;
case '\r':
this.buf.append("
");
break;
default:
if (((c >= 0x01 && c <= 0x1F && c != 0x09 && c != 0x0A) || (c >= 0x7F && c <= 0x9F) || c == 0x2028) || isAttributeData
&& (c == 0x09 || c == 0x0A)) {
this.buf.append("");
this.buf.append(Integer.toHexString(c).toUpperCase());
this.buf.append(";");
} else {
this.buf.append(c);
}
}
}
}
}