001/* 002 * Copyright (c) 2018-2024 Tada AB and other contributors, as listed below. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the The BSD 3-Clause License 006 * which accompanies this distribution, and is available at 007 * http://opensource.org/licenses/BSD-3-Clause 008 * 009 * Contributors: 010 * Chapman Flack 011 */ 012package org.postgresql.pljava.example.annotation; 013 014import java.sql.Connection; 015import java.sql.DriverManager; 016import java.sql.PreparedStatement; 017import java.sql.ResultSet; 018import java.sql.ResultSetMetaData; 019import java.sql.SQLData; 020import java.sql.SQLInput; 021import java.sql.SQLOutput; 022import java.sql.SQLXML; 023import java.sql.Statement; 024import java.sql.Types; 025 026import java.sql.SQLDataException; 027import java.sql.SQLException; 028 029import java.io.ByteArrayInputStream; 030import java.io.InputStream; 031import java.io.OutputStream; 032import java.io.Reader; 033import java.io.StringReader; 034import java.io.StringWriter; 035import java.io.Writer; 036 037import java.io.IOException; 038 039import java.util.List; 040import java.util.Map; 041import java.util.HashMap; 042 043import javax.xml.parsers.DocumentBuilderFactory; 044import javax.xml.parsers.ParserConfigurationException; 045 046import static javax.xml.transform.OutputKeys.ENCODING; 047import javax.xml.transform.Result; 048import javax.xml.transform.Source; 049import javax.xml.transform.Templates; 050import javax.xml.transform.Transformer; 051import javax.xml.transform.TransformerFactory; 052 053import javax.xml.transform.TransformerException; 054import javax.xml.transform.TransformerConfigurationException; 055 056import javax.xml.transform.stream.StreamResult; 057import javax.xml.transform.stream.StreamSource; 058import javax.xml.transform.dom.DOMResult; 059import javax.xml.transform.dom.DOMSource; 060import javax.xml.transform.sax.SAXResult; 061import javax.xml.transform.sax.SAXSource; 062import javax.xml.transform.stax.StAXResult; 063import javax.xml.transform.stax.StAXSource; 064 065import javax.xml.validation.Schema; 066import javax.xml.validation.SchemaFactory; 067 068import org.postgresql.pljava.Adjusting; 069import static org.postgresql.pljava.Adjusting.XML.setFirstSupported; 070import org.postgresql.pljava.annotation.Function; 071import org.postgresql.pljava.annotation.MappedUDT; 072import org.postgresql.pljava.annotation.SQLAction; 073import org.postgresql.pljava.annotation.SQLType; 074 075import static org.postgresql.pljava.example.LoggerTest.logMessage; 076 077/* Imports needed just for the SAX flavor of "low-level XML echo" below */ 078import javax.xml.parsers.ParserConfigurationException; 079import javax.xml.parsers.SAXParserFactory; 080import org.xml.sax.XMLReader; 081import org.xml.sax.ContentHandler; 082import org.xml.sax.DTDHandler; 083import org.xml.sax.ext.LexicalHandler; 084 085/* Imports needed just for the StAX flavor of "low-level XML echo" below */ 086import javax.xml.stream.XMLEventReader; 087import javax.xml.stream.XMLEventWriter; 088import javax.xml.stream.XMLInputFactory; 089import javax.xml.stream.XMLOutputFactory; 090import javax.xml.stream.XMLStreamException; 091import org.xml.sax.SAXException; 092 093/* Imports needed just for xmlTextNode below (serializing via SAX, StAX, DOM) */ 094import org.xml.sax.helpers.AttributesImpl; 095import org.w3c.dom.Document; 096import org.w3c.dom.DocumentFragment; 097import org.w3c.dom.bootstrap.DOMImplementationRegistry; 098 099 100/** 101 * Class illustrating use of {@link SQLXML} to operate on XML data. 102 *<p> 103 * This class also serves as the mapping class for a composite type 104 * {@code javatest.onexml}, the better to verify that {@link SQLData} 105 * input/output works too. That's why it has to implement SQLData. 106 *<p> 107 * Everything mentioning the type XML here needs a conditional implementor tag 108 * in case of being loaded into a PostgreSQL instance built without that type. 109 */ 110@SQLAction(provides="postgresql_xml", install= 111 "SELECT CASE (SELECT 1 FROM pg_type WHERE typname = 'xml') WHEN 1" + 112 " THEN set_config('pljava.implementors', 'postgresql_xml,' || " + 113 " current_setting('pljava.implementors'), true) " + 114 "END" 115) 116 117@SQLAction(implementor="postgresql_xml", requires="echoXMLParameter", 118 install= 119 "WITH" + 120 " s(how) AS (SELECT generate_series(1, 7))," + 121 " t(x) AS (" + 122 " SELECT table_to_xml('pg_catalog.pg_operator', true, false, '')" + 123 " )," + 124 " r(howin, howout, isdoc) AS (" + 125 " SELECT" + 126 " i.how, o.how," + 127 " javatest.echoxmlparameter(x, i.how, o.how) IS DOCUMENT" + 128 " FROM" + 129 " t, s AS i, s AS o" + 130 " WHERE" + 131 " NOT (i.how = 6 and o.how = 7)" + // 6->7 unreliable in some JREs 132 " ) " + 133 "SELECT" + 134 " CASE WHEN every(isdoc)" + 135 " THEN javatest.logmessage('INFO', 'SQLXML echos succeeded')" + 136 " ELSE javatest.logmessage('WARNING', 'SQLXML echos had problems')" + 137 " END " + 138 "FROM" + 139 " r" 140) 141 142@SQLAction(implementor="postgresql_xml", requires="proxiedXMLEcho", 143 install= 144 "WITH" + 145 " s(how) AS (SELECT unnest('{1,2,4,5,6,7}'::int[]))," + 146 " t(x) AS (" + 147 " SELECT table_to_xml('pg_catalog.pg_operator', true, false, '')" + 148 " )," + 149 " r(how, isdoc) AS (" + 150 " SELECT" + 151 " how," + 152 " javatest.proxiedxmlecho(x, how) IS DOCUMENT" + 153 " FROM" + 154 " t, s" + 155 " )" + 156 "SELECT" + 157 " CASE WHEN every(isdoc)" + 158 " THEN javatest.logmessage('INFO', 'proxied SQLXML echos succeeded')" + 159 " ELSE javatest.logmessage('WARNING'," + 160 " 'proxied SQLXML echos had problems')" + 161 " END " + 162 "FROM" + 163 " r" 164) 165 166@SQLAction(implementor="postgresql_xml", requires="lowLevelXMLEcho", 167 install={ 168 "SELECT" + 169 " preparexmlschema('schematest', $$" + 170 "<xs:schema" + 171 " xmlns:xs='http://www.w3.org/2001/XMLSchema'" + 172 " targetNamespace='urn:testme'" + 173 " elementFormDefault='qualified'>" + 174 " <xs:element name='row'>" + 175 " <xs:complexType>" + 176 " <xs:sequence>" + 177 " <xs:element name='textcol' type='xs:string' nillable='true'/>" + 178 " <xs:element name='intcol' type='xs:integer' nillable='true'/>" + 179 " </xs:sequence>" + 180 " </xs:complexType>" + 181 " </xs:element>" + 182 "</xs:schema>" + 183 "$$, 'http://www.w3.org/2001/XMLSchema', 5)", 184 185 "WITH" + 186 " s(how) AS (SELECT unnest('{4,5,7}'::int[]))," + 187 " r(isdoc) AS (" + 188 " SELECT" + 189 " javatest.lowlevelxmlecho(" + 190 " query_to_xml(" + 191 " 'SELECT ''hi'' AS textcol, 1 AS intcol', true, true, 'urn:testme'"+ 192 " ), how, params) IS DOCUMENT" + 193 " FROM" + 194 " s," + 195 " (SELECT 'schematest' AS schema) AS params" + 196 " )" + 197 "SELECT" + 198 " CASE WHEN every(isdoc)" + 199 " THEN javatest.logmessage('INFO', 'XML Schema tests succeeded')" + 200 " ELSE javatest.logmessage('WARNING'," + 201 " 'XML Schema tests had problems')" + 202 " END " + 203 "FROM" + 204 " r" 205 } 206) 207 208@SQLAction(implementor="postgresql_xml", 209 requires={"prepareXMLTransform", "transformXML"}, 210 install={ 211 "REVOKE EXECUTE ON FUNCTION javatest.prepareXMLTransformWithJava" + 212 " (pg_catalog.varchar, pg_catalog.xml, integer, boolean, boolean," + 213 " pg_catalog.RECORD)" + 214 " FROM PUBLIC", 215 216 "SELECT" + 217 " javatest.prepareXMLTransform('distinctElementNames'," + 218 "'<xsl:transform version=''1.0''" + 219 " xmlns:xsl=''http://www.w3.org/1999/XSL/Transform''" + 220 " xmlns:exsl=''http://exslt.org/common''" + 221 " xmlns:set=''http://exslt.org/sets''" + 222 " extension-element-prefixes=''exsl set''" + 223 ">" + 224 " <xsl:output method=''xml'' indent=''no''/>" + 225 " <xsl:template match=''/''>" + 226 " <xsl:variable name=''enames''>" + 227 " <xsl:for-each select=''//*''>" + 228 " <ename><xsl:value-of select=''local-name()''/></ename>" + 229 " </xsl:for-each>" + 230 " </xsl:variable>" + 231 " <xsl:for-each" + 232 " select=''set:distinct(exsl:node-set($enames)/ename)''>" + 233 " <xsl:sort select=''string()''/>" + 234 " <den><xsl:value-of select=''.''/></den>" + 235 " </xsl:for-each>" + 236 " </xsl:template>" + 237 "</xsl:transform>', how => 5, enableExtensionFunctions => true)", 238 239 "SELECT" + 240 " javatest.prepareXMLTransformWithJava('getPLJavaVersion'," + 241 "'<xsl:transform version=''1.0''" + 242 " xmlns:xsl=''http://www.w3.org/1999/XSL/Transform''" + 243 " xmlns:java=''http://xml.apache.org/xalan/java''" + 244 " exclude-result-prefixes=''java''" + 245 ">" + 246 " <xsl:template match=''/''>" + 247 " <xsl:value-of" + 248 " select=''java:java.lang.System.getProperty(" + 249 " \"org.postgresql.pljava.version\")''" + 250 " />" + 251 " </xsl:template>" + 252 "</xsl:transform>', enableExtensionFunctions => true)", 253 254 "SELECT" + 255 " CASE WHEN" + 256 " javatest.transformXML('distinctElementNames'," + 257 " '<a><c/><e/><b/><b/><d/></a>', 5, 5)::text" + 258 " =" + 259 " '<den>a</den><den>b</den><den>c</den><den>d</den><den>e</den>'"+ 260 " THEN javatest.logmessage('INFO', 'XSLT 1.0 test succeeded')" + 261 " ELSE javatest.logmessage('WARNING', 'XSLT 1.0 test failed')" + 262 " END", 263 264 "SELECT" + 265 " CASE WHEN" + 266 " javatest.transformXML('getPLJavaVersion', '')::text" + 267 " OPERATOR(pg_catalog.=) extversion" + 268 " THEN javatest.logmessage('INFO', 'XSLT 1.0 with Java succeeded')" + 269 " ELSE javatest.logmessage('WARNING', 'XSLT 1.0 with Java failed')" + 270 " END" + 271 " FROM pg_catalog.pg_extension" + 272 " WHERE extname = 'pljava'" 273 } 274) 275 276@SQLAction(implementor="postgresql_xml", 277 provides="xml_java_ge_22", requires="javaSpecificationGE", install= 278 "SELECT CASE WHEN" + 279 " javatest.javaSpecificationGE('22')" + 280 " THEN set_config('pljava.implementors', 'xml_java_ge_22,' || " + 281 " current_setting('pljava.implementors'), true) " + 282 "END" 283) 284 285@SQLAction(implementor="xml_java_ge_22", requires="lowLevelXMLEcho", install= 286 "WITH" + 287 " s(how) AS (SELECT unnest('{5,6,7}'::int[]))," + 288 " r(isdoc) AS (" + 289 " SELECT" + 290 " javatest.lowlevelxmlecho(" + 291 /* 292 * A truly minimal DTD, <!DOCTYPE a>, cannot be ignored by Java 22's SAX/DOM 293 * parser (though it can be, when using the StAX API). NullPointerException 294 * calling getActiveGrammar().isImmutable() is the result. Bug: JDK-8329295 295 * Including either an externalID or an internal subset (like the empty [] 296 * here) avoids the issue. 297 */ 298 " '<!DOCTYPE a []><a/>'::xml, how, params) IS DOCUMENT" + 299 " FROM" + 300 " s," + 301 " (SELECT null::void AS ignoreDTD) AS params" + 302 " )" + 303 "SELECT" + 304 " CASE WHEN every(isdoc)" + 305 " THEN javatest.logmessage('INFO', 'jdk.xml.dtd.support=ignore OK')" + 306 " ELSE javatest.logmessage('WARNING', 'jdk.xml.dtd.support=ignore NG')" + 307 " END " + 308 "FROM" + 309 " r" 310) 311 312@MappedUDT(schema="javatest", name="onexml", structure="c1 xml", 313 implementor="postgresql_xml", 314 comment="A composite type mapped by the PassXML example class") 315public class PassXML implements SQLData 316{ 317 static SQLXML s_sx; 318 319 static TransformerFactory s_tf = TransformerFactory.newDefaultInstance(); 320 321 static Map<String,Templates> s_tpls = new HashMap<>(); 322 323 static Map<String,Schema> s_schemas = new HashMap<>(); 324 325 @Function(schema="javatest", implementor="postgresql_xml") 326 public static String inXMLoutString(SQLXML in) throws SQLException 327 { 328 return in.getString(); 329 } 330 331 @Function(schema="javatest", implementor="postgresql_xml") 332 public static SQLXML inStringoutXML(String in) throws SQLException 333 { 334 Connection c = DriverManager.getConnection("jdbc:default:connection"); 335 SQLXML result = c.createSQLXML(); 336 result.setString(in); 337 return result; 338 } 339 340 /** 341 * Echo an XML parameter back, exercising seven different ways 342 * (howin => 1-7) of reading an SQLXML object, and seven 343 * (howout => 1-7) of returning one. 344 *<p> 345 * If howin => 0, the XML parameter is simply saved in a static. It can 346 * be read in a subsequent call with sx => null, but only in the same 347 * transaction. 348 *<p> 349 * The "echoing" is done (in the {@code echoXML} method below) using a 350 * {@code Transformer}, that is, the "TrAX" Transformation API for XML 351 * supplied in Java. It illustrates how an identity {@code Transformer} can 352 * be used to get the XML content from the source to the result for any of 353 * the APIs selectable by howin and howout. 354 *<p> 355 * It also illustrates something else. When using StAX (6 for howin 356 * or howout) and XML of the {@code CONTENT} flavor (multiple top-level 357 * elements, characters outside the top element, etc.), it is easy to 358 * construct examples that fail. The fault is not really with the StAX API, 359 * nor with TrAX proper, but with the small handful of bridge classes that 360 * were added to the JRE with StAX's first appearance, to make it 361 * interoperate with TrAX. It is not that those classes completely overlook 362 * the {@code CONTENT} case: they make some efforts to handle it. Just not 363 * the right ones, and given the Java developers' usual reluctance to change 364 * such longstanding behavior, that's probably not getting fixed. 365 *<p> 366 * Moral: StAX is a nice API, have no fear to use it directly in 367 * freshly-developed code, but: when using TrAX, make every effort to supply 368 * a {@code Transformer} with {@code Source} and {@code Result} objects of 369 * <em>any</em> kind other than StAX. 370 */ 371 @Function(schema="javatest", implementor="postgresql_xml", 372 provides="echoXMLParameter") 373 public static SQLXML echoXMLParameter(SQLXML sx, int howin, int howout) 374 throws SQLException 375 { 376 if ( null == sx ) 377 sx = s_sx; 378 if ( 0 == howin ) 379 { 380 s_sx = sx; 381 return null; 382 } 383 return echoSQLXML(sx, howin, howout); 384 } 385 386 /** 387 * Echo an XML parameter back, but with parameter and return types of 388 * PostgreSQL {@code text}. 389 *<p> 390 * The other version of this method needs a conditional implementor tag 391 * because it cannot be declared in a PostgreSQL instance that was built 392 * without {@code libxml} support and the PostgreSQL {@code XML} type. 393 * But this version can, simply by mapping the {@code SQLXML} parameter 394 * and return types to the SQL {@code text} type. The Java code is no 395 * different. 396 *<p> 397 * Note that it's possible for both declarations to coexist in PostgreSQL 398 * (because as far as it is concerned, their signatures are different), but 399 * these two Java methods cannot have the same name (because they differ 400 * only in annotations, not in the declared Java types). So, this one needs 401 * a slightly tweaked name, and a {@code name} attribute in the annotation 402 * so PostgreSQL sees the right name. 403 */ 404 @Function(schema="javatest", name="echoXMLParameter", type="text") 405 public static SQLXML echoXMLParameter_( 406 @SQLType("text") SQLXML sx, int howin, int howout) 407 throws SQLException 408 { 409 return echoXMLParameter(sx, howin, howout); 410 } 411 412 /** 413 * "Echo" an XML parameter not by creating a new writable {@code SQLXML} 414 * object at all, but simply returning the passed-in readable one untouched. 415 */ 416 @Function(schema="javatest", implementor="postgresql_xml") 417 public static SQLXML bounceXMLParameter(SQLXML sx) throws SQLException 418 { 419 return sx; 420 } 421 422 /** 423 * Just like {@link bounceXMLParameter} but with parameter and return typed 424 * as {@code text}, and so usable on a PostgreSQL instance lacking the XML 425 * type. 426 */ 427 @Function(schema="javatest", type="text", name="bounceXMLParameter") 428 public static SQLXML bounceXMLParameter_(@SQLType("text") SQLXML sx) 429 throws SQLException 430 { 431 return sx; 432 } 433 434 /** 435 * Just like {@link bounceXMLParameter} but with the parameter typed as 436 * {@code text} and the return type left as XML, so functions as a cast. 437 *<p> 438 * Slower than the other cases, because it must verify that the input really 439 * is XML before blindly calling it a PostgreSQL XML type. But the speed 440 * compares respectably to PostgreSQL's own CAST(text AS xml), at least for 441 * larger values; I am seeing Java pull ahead right around 32kB of XML data 442 * and beat PG by a factor of 2 or better at sizes of 1 or 2 MB. 443 * Unsurprisingly, PG has the clear advantage when values are very short. 444 */ 445 @Function(schema="javatest", implementor="postgresql_xml") 446 public static SQLXML castTextXML(@SQLType("text") SQLXML sx) 447 throws SQLException 448 { 449 return sx; 450 } 451 452 /** 453 * Precompile an XSL transform {@code source} and save it (for the 454 * current session) as {@code name}. 455 *<p> 456 * Each value of {@code how}, 1-7, selects a different way of presenting 457 * the {@code SQLXML} object to the XSL processor. 458 *<p> 459 * Passing {@code true} for {@code enableExtensionFunctions} allows the 460 * transform to use extensions that the Java XSLT implementation supports, 461 * such as functions from EXSLT. Those are disabled by default. 462 *<p> 463 * Passing {@code false} for {@code builtin} will allow a 464 * {@code TransformerFactory} other than Java's built-in one to be found 465 * using the usual search order and the context class loader (normally 466 * the PL/Java class path for the schema where this function is declared). 467 * The default of {@code true} ensures that the built-in Java XSLT 1.0 468 * implementation is used. A transformer implementation other than Xalan 469 * may not recognize the feature controlled by 470 * {@code enableExtensionFunctions}, so failure to configure that feature 471 * will be logged as a warning if {@code builtin} is {@code false}, instead 472 * of thrown as an exception. 473 *<p> 474 * Out of the box, Java's transformers only support XSLT 1.0. See the S9 475 * example for more capabilities (at the cost of downloading the Saxon jar). 476 */ 477 @Function(schema="javatest", implementor="postgresql_xml", 478 provides="prepareXMLTransform") 479 public static void prepareXMLTransform(String name, SQLXML source, 480 @SQLType(defaultValue="0") int how, 481 @SQLType(defaultValue="false") boolean enableExtensionFunctions, 482 @SQLType(defaultValue="true") boolean builtin, 483 @SQLType(defaultValue={}) ResultSet adjust) 484 throws SQLException 485 { 486 prepareXMLTransform( 487 name, source, how, enableExtensionFunctions, adjust, builtin, 488 /* withJava */ false); 489 } 490 491 /** 492 * Precompile an XSL transform {@code source} and save it (for the 493 * current session) as {@code name}, where the transform may call Java 494 * methods. 495 *<p> 496 * Otherwise identical to {@code prepareXMLTransform}, this version sets the 497 * {@code TransformerFactory}'s {@code extensionClassLoader} (to the context 498 * class loader, normally the PL/Java class path for the schema where this 499 * function is declared), so the transform will be able to use 500 * xalan's Java call syntax to call any public Java methods that would be 501 * accessible to this class. (That can make a big difference in usefulness 502 * for the otherwise rather limited XSLT 1.0.) 503 *<p> 504 * As with {@code enableExtensionFunctions}, failure by the transformer 505 * implementation to recognize or allow the {@code extensionClassLoader} 506 * property will be logged as a warning if {@code builtin} is {@code false}, 507 * rather than thrown as an exception. 508 *<p> 509 * This example function will be installed with {@code EXECUTE} permission 510 * revoked from {@code PUBLIC}, as it essentially confers the ability to 511 * create arbitrary new Java functions, so should only be granted to roles 512 * you would be willing to grant {@code USAGE ON LANGUAGE java}. 513 *<p> 514 * Because this function only prepares the transform, and 515 * {@link #transformXML transformXML} applies it, there is some division of 516 * labor in determining what limits apply to its behavior. The use of this 517 * method instead of {@code prepareXMLTransform} determines whether the 518 * transform is allowed to see external Java methods at all; it will be 519 * the policy permissions granted to {@code transformXML} that control what 520 * those methods can do when the transform is applied. For now, that method 521 * is defined in the trusted/sandboxed {@code java} language, so this 522 * function could reasonably be granted to any role with {@code USAGE} on 523 * {@code java}. If, by contrast, {@code transformXML} were declared in the 524 * 'untrusted' {@code javaU}, it would be prudent to allow only superusers 525 * access to this function, just as only they can {@code CREATE FUNCTION} in 526 * an untrusted language. 527 */ 528 @Function(schema="javatest", implementor="postgresql_xml", 529 provides="prepareXMLTransform") 530 public static void prepareXMLTransformWithJava(String name, SQLXML source, 531 @SQLType(defaultValue="0") int how, 532 @SQLType(defaultValue="false") boolean enableExtensionFunctions, 533 @SQLType(defaultValue="true") boolean builtin, 534 @SQLType(defaultValue={}) ResultSet adjust) 535 throws SQLException 536 { 537 prepareXMLTransform( 538 name, source, how, enableExtensionFunctions, adjust, builtin, 539 /* withJava */ true); 540 } 541 542 private static void prepareXMLTransform(String name, SQLXML source, int how, 543 boolean enableExtensionFunctions, ResultSet adjust, boolean builtin, 544 boolean withJava) 545 throws SQLException 546 { 547 TransformerFactory tf = 548 builtin 549 ? TransformerFactory.newDefaultInstance() 550 : TransformerFactory.newInstance(); 551 552 String legacy_pfx = "http://www.oracle.com/xml/jaxp/properties/"; 553 String java17_pfx = "jdk.xml."; 554 String exf_sfx = "enableExtensionFunctions"; 555 556 String ecl_legacy = "jdk.xml.transform.extensionClassLoader"; 557 String ecl_java17 = "jdk.xml.extensionClassLoader"; 558 559 Source src = sxToSource(source, how, adjust); 560 561 try 562 { 563 Exception e; 564 565 e = setFirstSupported(tf::setFeature, enableExtensionFunctions, 566 List.of(TransformerConfigurationException.class), null, 567 java17_pfx + exf_sfx, legacy_pfx + exf_sfx); 568 569 if ( null != e ) 570 { 571 if ( builtin ) 572 throw new SQLException( 573 "Configuring XML transformation: " + e.getMessage(), e); 574 else 575 logMessage("WARNING", 576 "non-builtin transformer: ignoring " + e.getMessage()); 577 } 578 579 if ( withJava ) 580 { 581 e = setFirstSupported(tf::setAttribute, 582 Thread.currentThread().getContextClassLoader(), 583 List.of(IllegalArgumentException.class), null, 584 ecl_java17, ecl_legacy); 585 586 if ( null != e ) 587 { 588 if ( builtin ) 589 throw new SQLException( 590 "Configuring XML transformation: " + 591 e.getMessage(), e); 592 else 593 logMessage("WARNING", 594 "non-builtin transformer: ignoring " + 595 e.getMessage()); 596 } 597 } 598 599 s_tpls.put(name, tf.newTemplates(src)); 600 } 601 catch ( TransformerException te ) 602 { 603 throw new SQLException( 604 "Preparing XML transformation: " + te.getMessage(), te); 605 } 606 } 607 608 /** 609 * Transform some XML according to a named transform prepared with 610 * {@code prepareXMLTransform}. 611 *<p> 612 * Pass null for {@code transformName} to get a plain identity transform 613 * (not such an interesting thing to do, unless you also specify indenting). 614 */ 615 @Function(schema="javatest", implementor="postgresql_xml", 616 provides="transformXML") 617 public static SQLXML transformXML( 618 String transformName, SQLXML source, 619 @SQLType(defaultValue="0") int howin, 620 @SQLType(defaultValue="0") int howout, 621 @SQLType(defaultValue={}) ResultSet adjust, 622 @SQLType(optional=true) Boolean indent, 623 @SQLType(optional=true) Integer indentWidth) 624 throws SQLException 625 { 626 Templates tpl = null == transformName? null: s_tpls.get(transformName); 627 Source src = sxToSource(source, howin, adjust); 628 629 if ( Boolean.TRUE.equals(indent) && 0 == howout ) 630 howout = 4; // transformer only indents if writing a StreamResult 631 632 Connection c = DriverManager.getConnection("jdbc:default:connection"); 633 SQLXML result = c.createSQLXML(); 634 Result rlt = sxToResult(result, howout, adjust); 635 636 try 637 { 638 Transformer t = 639 null == tpl ? s_tf.newTransformer() : tpl.newTransformer(); 640 /* 641 * For the non-SAX/StAX/DOM flavors of output, you're responsible 642 * for setting the Transformer to use the server encoding. 643 */ 644 if ( rlt instanceof StreamResult ) 645 t.setOutputProperty(ENCODING, 646 System.getProperty("org.postgresql.server.encoding")); 647 else if ( Boolean.TRUE.equals(indent) ) 648 logMessage("WARNING", 649 "indent requested, but howout specifies a non-stream " + 650 "Result type; no indenting will happen"); 651 652 if ( null != indent ) 653 t.setOutputProperty("indent", indent ? "yes" : "no"); 654 if ( null != indentWidth ) 655 t.setOutputProperty( 656 "{http://xml.apache.org/xalan}indent-amount", 657 "" + indentWidth); 658 659 t.transform(src, rlt); 660 } 661 catch ( TransformerException te ) 662 { 663 throw new SQLException("Transforming XML: " + te.getMessage(), te); 664 } 665 666 return ensureClosed(rlt, result, howout); 667 } 668 669 /** 670 * Precompile a schema {@code source} in schema language {@code lang} 671 * and save it (for the current session) as {@code name}. 672 *<p> 673 * Each value of {@code how}, 1-7, selects a different way of presenting 674 * the {@code SQLXML} object to the schema parser. 675 *<p> 676 * The {@code lang} parameter is a URI that identifies a known schema 677 * language. The only language a Java runtime is required to support is 678 * W3C XML Schema 1.0, with URI {@code http://www.w3.org/2001/XMLSchema}. 679 */ 680 @Function(schema="javatest", implementor="postgresql_xml") 681 public static void prepareXMLSchema( 682 String name, SQLXML source, String lang, int how) 683 throws SQLException 684 { 685 try 686 { 687 s_schemas.put(name, 688 SchemaFactory.newInstance(lang) 689 .newSchema(sxToSource(source, how))); 690 } 691 catch ( SAXException e ) 692 { 693 throw new SQLException( 694 "failed to prepare schema: " + e.getMessage(), e); 695 } 696 } 697 698 private static SQLXML echoSQLXML(SQLXML sx, int howin, int howout) 699 throws SQLException 700 { 701 Connection c = DriverManager.getConnection("jdbc:default:connection"); 702 SQLXML rx = c.createSQLXML(); 703 Source src = sxToSource(sx, howin); 704 Result rlt = sxToResult(rx, howout); 705 706 try 707 { 708 Transformer t = s_tf.newTransformer(); 709 /* 710 * For the non-SAX/StAX/DOM flavors of output, you're responsible 711 * for setting the Transformer to use the server encoding. 712 */ 713 if ( howout < 5 ) 714 t.setOutputProperty(ENCODING, 715 System.getProperty("org.postgresql.server.encoding")); 716 t.transform(src, rlt); 717 } 718 catch ( TransformerException te ) 719 { 720 throw new SQLException("XML transformation failed", te); 721 } 722 723 return ensureClosed(rlt, rx, howout); 724 } 725 726 /** 727 * Echo the XML parameter back, using lower-level manipulations than 728 * {@code echoXMLParameter}. 729 *<p> 730 * This illustrates how the simple use of {@code t.transform(src,rlt)} 731 * in {@code echoSQLXML} substitutes for a lot of fiddly case-by-case code, 732 * but when coding for a specific case, all the generality of {@code 733 * transform} may not be needed. It can be interesting to compare memory use 734 * when XML values are large. 735 *<p> 736 * This method has been revised to demonstrate, even for low-level 737 * manipulations, how much fiddliness can now be avoided through use of the 738 * {@link Adjusting.XML.SourceResult} class, and how to make adjustments to 739 * parsing restrictions by passing the optional row-typed parameter 740 * <em>adjust</em>, which defaults to an empty row. For example, passing 741 *<pre> 742 * adjust => (select a from 743 * (true as allowdtd, true as expandentityreferences) as a) 744 *</pre> 745 * would allow a document that contains an internal DTD subset and uses 746 * entities defined there. 747 *<p> 748 * The older, pre-{@code SourceResult} code for doing low-level XML echo 749 * has been moved to the {@code oldSchoolLowLevelEcho} method below. It can 750 * still be exercised by calling this method, explicitly passing 751 * {@code adjust => NULL}. 752 */ 753 @Function(schema="javatest", implementor="postgresql_xml", 754 provides="lowLevelXMLEcho") 755 public static SQLXML lowLevelXMLEcho( 756 SQLXML sx, int how, @SQLType(defaultValue={}) ResultSet adjust) 757 throws SQLException 758 { 759 Connection c = DriverManager.getConnection("jdbc:default:connection"); 760 SQLXML rx = c.createSQLXML(); 761 762 if ( null == adjust ) 763 return oldSchoolLowLevelEcho(rx, sx, how); 764 765 Adjusting.XML.SourceResult axsr = 766 rx.setResult(Adjusting.XML.SourceResult.class); 767 768 switch ( how ) 769 { 770 /* 771 * The first four cases all present the content as unparsed bytes or 772 * characters, so there is nothing to adjust on the source side. 773 */ 774 case 1: 775 axsr.set(new StreamSource(sx.getBinaryStream())); 776 break; 777 case 2: 778 axsr.set(new StreamSource(sx.getCharacterStream())); 779 break; 780 case 3: 781 axsr.set(sx.getString()); 782 break; 783 case 4: 784 axsr.set(sx.getSource(StreamSource.class)); 785 break; 786 /* 787 * The remaining cases present the content in parsed form, and therefore 788 * may involve parsers that can be adjusted according to the supplied 789 * preferences. 790 */ 791 case 5: 792 axsr.set(applyAdjustments(adjust, 793 sx.getSource(Adjusting.XML.SAXSource.class))); 794 break; 795 case 6: 796 axsr.set(applyAdjustments(adjust, 797 sx.getSource(Adjusting.XML.StAXSource.class))); 798 break; 799 case 7: 800 axsr.set(applyAdjustments(adjust, 801 sx.getSource(Adjusting.XML.DOMSource.class))); 802 break; 803 default: 804 throw new SQLDataException( 805 "how must be 1-7 for lowLevelXMLEcho", "22003"); 806 } 807 808 /* 809 * Adjustments can also be applied to the SourceResult itself, where 810 * they will affect any implicitly-created parser used to verify or 811 * re-encode the content, if it was supplied in unparsed form. 812 */ 813 return applyAdjustments(adjust, axsr).get().getSQLXML(); 814 } 815 816 /** 817 * Apply adjustments (supplied as a row type with a named column for each 818 * desired adjustment and its value) to an instance of 819 * {@link Adjusting.XML.Parsing}. 820 *<p> 821 * Column names in the <em>adjust</em> row are case-insensitive versions of 822 * the method names in {@link Adjusting.XML.Parsing}, and the value of each 823 * column should be of the appropriate type (if the method has a parameter). 824 * @param adjust A row type as described above, possibly of no columns if no 825 * adjustments are wanted 826 * @param axp An instance of Adjusting.XML.Parsing 827 * @return axp, after applying any adjustments 828 */ 829 public static <T extends Adjusting.XML.Parsing<? super T>> 830 T applyAdjustments(ResultSet adjust, T axp) 831 throws SQLException 832 { 833 ResultSetMetaData rsmd = adjust.getMetaData(); 834 int n = rsmd.getColumnCount(); 835 836 for ( int i = 1; i <= n; ++i ) 837 { 838 String k = rsmd.getColumnLabel(i); 839 if ( "lax".equalsIgnoreCase(k) ) 840 axp.lax(adjust.getBoolean(i)); 841 else if ( "allowDTD".equalsIgnoreCase(k) ) 842 axp.allowDTD(adjust.getBoolean(i)); 843 else if ( "ignoreDTD".equalsIgnoreCase(k) ) 844 axp.ignoreDTD(); 845 else if ( "externalGeneralEntities".equalsIgnoreCase(k) ) 846 axp.externalGeneralEntities(adjust.getBoolean(i)); 847 else if ( "externalParameterEntities".equalsIgnoreCase(k) ) 848 axp.externalParameterEntities(adjust.getBoolean(i)); 849 else if ( "loadExternalDTD".equalsIgnoreCase(k) ) 850 axp.loadExternalDTD(adjust.getBoolean(i)); 851 else if ( "xIncludeAware".equalsIgnoreCase(k) ) 852 axp.xIncludeAware(adjust.getBoolean(i)); 853 else if ( "expandEntityReferences".equalsIgnoreCase(k) ) 854 axp.expandEntityReferences(adjust.getBoolean(i)); 855 else if ( "elementAttributeLimit".equalsIgnoreCase(k) ) 856 axp.elementAttributeLimit(adjust.getInt(i)); 857 else if ( "entityExpansionLimit".equalsIgnoreCase(k) ) 858 axp.entityExpansionLimit(adjust.getInt(i)); 859 else if ( "entityReplacementLimit".equalsIgnoreCase(k) ) 860 axp.entityReplacementLimit(adjust.getInt(i)); 861 else if ( "maxElementDepth".equalsIgnoreCase(k) ) 862 axp.maxElementDepth(adjust.getInt(i)); 863 else if ( "maxGeneralEntitySizeLimit".equalsIgnoreCase(k) ) 864 axp.maxGeneralEntitySizeLimit(adjust.getInt(i)); 865 else if ( "maxParameterEntitySizeLimit".equalsIgnoreCase(k) ) 866 axp.maxParameterEntitySizeLimit(adjust.getInt(i)); 867 else if ( "maxXMLNameLimit".equalsIgnoreCase(k) ) 868 axp.maxXMLNameLimit(adjust.getInt(i)); 869 else if ( "totalEntitySizeLimit".equalsIgnoreCase(k) ) 870 axp.totalEntitySizeLimit(adjust.getInt(i)); 871 else if ( "accessExternalDTD".equalsIgnoreCase(k) ) 872 axp.accessExternalDTD(adjust.getString(i)); 873 else if ( "accessExternalSchema".equalsIgnoreCase(k) ) 874 axp.accessExternalSchema(adjust.getString(i)); 875 else if ( "schema".equalsIgnoreCase(k) ) 876 { 877 try 878 { 879 axp.schema(s_schemas.get(adjust.getString(i))); 880 } 881 catch (UnsupportedOperationException e) 882 { 883 } 884 } 885 else 886 throw new SQLDataException( 887 "unrecognized name \"" + k + "\" for parser adjustment", 888 "22000"); 889 } 890 return axp; 891 } 892 893 /** 894 * An obsolescent example, showing what was required to copy from one 895 * {@code SQLXML} object to another, using the various supported APIs, 896 * without using {@link Adjusting.XML.SourceResult}, or at least without 897 * using it much. It is still used in case 4 to be sure of getting a 898 * {@code StreamResult} that matches the byte-or-character-ness of the 899 * {@code StreamSource}. How to handle that case without 900 * {@code SourceResult} is left as an exercise. 901 */ 902 private static SQLXML oldSchoolLowLevelEcho(SQLXML rx, SQLXML sx, int how) 903 throws SQLException 904 { 905 try 906 { 907 switch ( how ) 908 { 909 case 1: 910 InputStream is = sx.getBinaryStream(); 911 OutputStream os = rx.setBinaryStream(); 912 shovelBytes(is, os); 913 break; 914 case 2: 915 Reader r = sx.getCharacterStream(); 916 Writer w = rx.setCharacterStream(); 917 shovelChars(r, w); 918 break; 919 case 3: 920 rx.setString(sx.getString()); 921 break; 922 case 4: 923 StreamSource ss = sx.getSource(StreamSource.class); 924 Adjusting.XML.StreamResult sr = 925 rx.setResult(Adjusting.XML.StreamResult.class); 926 is = ss.getInputStream(); 927 r = ss.getReader(); 928 if ( null != is ) 929 { 930 os = sr.preferBinaryStream().get().getOutputStream(); 931 shovelBytes(is, os); 932 break; 933 } 934 if ( null != r ) 935 { 936 w = sr.preferCharacterStream().get().getWriter(); 937 shovelChars(r, w); 938 break; 939 } 940 throw new SQLDataException( 941 "StreamSource contained neither InputStream nor Reader"); 942 case 5: 943 SAXSource sxs = sx.getSource(SAXSource.class); 944 SAXResult sxr = rx.setResult(SAXResult.class); 945 XMLReader xr = sxs.getXMLReader(); 946 if ( null == xr ) 947 { 948 SAXParserFactory spf = SAXParserFactory.newInstance(); 949 spf.setNamespaceAware(true); 950 xr = spf.newSAXParser().getXMLReader(); 951 /* 952 * Important: before copying this example code for another 953 * use, consider whether the input XML might be untrusted. 954 * If so, the new XMLReader created here should have several 955 * features given safe default settings as outlined in the 956 * OWASP guidelines. (This branch is not reached when sx is 957 * a PL/Java native SQLXML instance, as xr will be non-null 958 * and already configured.) 959 */ 960 } 961 ContentHandler ch = sxr.getHandler(); 962 xr.setContentHandler(ch); 963 if ( ch instanceof DTDHandler ) 964 xr.setDTDHandler((DTDHandler)ch); 965 LexicalHandler lh = sxr.getLexicalHandler(); 966 if ( null == lh && ch instanceof LexicalHandler ) 967 lh = (LexicalHandler)ch; 968 if ( null != lh ) 969 xr.setProperty( 970 "http://xml.org/sax/properties/lexical-handler", lh); 971 xr.parse(sxs.getInputSource()); 972 break; 973 case 6: 974 StAXSource sts = sx.getSource(StAXSource.class); 975 StAXResult str = rx.setResult(StAXResult.class); 976 XMLOutputFactory xof = XMLOutputFactory.newInstance(); 977 /* 978 * The Source has either an event reader or a stream reader. Use 979 * the event reader directly, or create one around the stream 980 * reader. 981 */ 982 XMLEventReader xer = sts.getXMLEventReader(); 983 if ( null == xer ) 984 { 985 XMLInputFactory xif = XMLInputFactory .newInstance(); 986 xif.setProperty(xif.IS_NAMESPACE_AWARE, true); 987 /* 988 * Important: before copying this example code for another 989 * use, consider whether the input XML might be untrusted. 990 * If so, the new XMLInputFactory created here might want 991 * several properties given safe default settings as 992 * outlined in the OWASP guidelines. (When sx is a PL/Java 993 * native SQLXML instance, the XMLStreamReader obtained 994 * below will already have been so configured.) 995 */ 996 xer = xif.createXMLEventReader(sts.getXMLStreamReader()); 997 } 998 /* 999 * Were you thinking the above could be simply 1000 * createXMLEventReader(sts) by analogy with the writer below? 1001 * Good thought, but the XMLInputFactory implementation that's 1002 * included in OpenJDK doesn't implement the case where the 1003 * Source argument is a StAXSource! Two lines would do it. 1004 */ 1005 /* 1006 * Because of a regression in Java 9 and later, the line below, 1007 * while working in Java 8 and earlier, will produce a 1008 * ClassCastException in Java 9 through (for sure) 12, (almost 1009 * certainly) 13, and on until some future version fixes the 1010 * regression, if ever, if 'str' wraps any XMLStreamWriter 1011 * implementation other than the inaccessible one from the guts 1012 * of the JDK itself. The bug has been reported but (as of this 1013 * writing) is still in the maddening limbo phase of the Java 1014 * bug reporting cycle, where no bug number can refer to it. See 1015 * lowLevelXMLEcho() above for code to do this copy successfully 1016 * using an Adjusting.XML.SourceResult. 1017 */ 1018 XMLEventWriter xew = xof.createXMLEventWriter(str); 1019 xew.add(xer); 1020 xew.close(); 1021 xer.close(); 1022 break; 1023 case 7: 1024 DOMSource ds = sx.getSource(DOMSource.class); 1025 DOMResult dr = rx.setResult(DOMResult.class); 1026 dr.setNode(ds.getNode()); 1027 break; 1028 default: 1029 throw new SQLDataException( 1030 "how must be 1-7 for lowLevelXMLEcho", "22003"); 1031 } 1032 } 1033 catch ( IOException e ) 1034 { 1035 throw new SQLException( 1036 "IOException in lowLevelXMLEcho", "58030", e); 1037 } 1038 catch ( 1039 ParserConfigurationException | SAXException | XMLStreamException e ) 1040 { 1041 throw new SQLException( 1042 "XML exception in lowLevelXMLEcho", "22000", e); 1043 } 1044 return rx; 1045 } 1046 1047 /** 1048 * Proxy a PL/Java SQLXML source object as if it were of a non-PL/Java 1049 * implementing class, to confirm that it can still be returned successfully 1050 * to PostgreSQL. 1051 * @param sx readable {@code SQLXML} object to proxy 1052 * @param how 1,2,4,5,6,7 determines what subclass of {@code Source} will be 1053 * returned by {@code getSource}. 1054 */ 1055 @Function(schema="javatest", implementor="postgresql_xml", 1056 provides="proxiedXMLEcho") 1057 public static SQLXML proxiedXMLEcho(SQLXML sx, int how) 1058 throws SQLException 1059 { 1060 return new SQLXMLProxy(sx, how); 1061 } 1062 1063 /** 1064 * Supply a sequence of bytes to be the exact (encoded) content of an XML 1065 * value, which will be returned; if the encoding is not UTF-8, the value 1066 * should begin with an XML Decl that names the encoding. 1067 *<p> 1068 * Constructs an {@code SQLXML} instance that will return the supplied 1069 * content as a {@code StreamSource} wrapping an {@code InputStream}, or via 1070 * {@code getBinaryStream}, but fail if asked for any other form. 1071 */ 1072 @Function(schema="javatest", implementor="postgresql_xml", 1073 provides="mockedXMLEchoB") 1074 public static SQLXML mockedXMLEcho(byte[] bytes) 1075 throws SQLException 1076 { 1077 return new SQLXMLMock(bytes); 1078 } 1079 1080 /** 1081 * Supply a sequence of characters to be the exact (Unicode) content of an 1082 * XML value, which will be returned; if the value begins with an XML Decl 1083 * that names an encoding, the content will be assumed to contain only 1084 * characters representable in that encoding. 1085 *<p> 1086 * Constructs an {@code SQLXML} instance that will return the supplied 1087 * content as a {@code StreamSource} wrapping a {@code Reader}, or via 1088 * {@code getCharacterStream}, but fail if asked for any other form. 1089 */ 1090 @Function(schema="javatest", implementor="postgresql_xml", 1091 provides="mockedXMLEchoC") 1092 public static SQLXML mockedXMLEcho(String chars) 1093 throws SQLException 1094 { 1095 return new SQLXMLMock(chars); 1096 } 1097 1098 /** 1099 * Text-typed variant of lowLevelXMLEcho (does not require XML type). 1100 */ 1101 @Function(schema="javatest", name="lowLevelXMLEcho", 1102 type="text") 1103 public static SQLXML lowLevelXMLEcho_(@SQLType("text") SQLXML sx, int how, 1104 @SQLType(defaultValue={}) ResultSet adjust) 1105 throws SQLException 1106 { 1107 return lowLevelXMLEcho(sx, how, adjust); 1108 } 1109 1110 /** 1111 * Low-level XML echo where the Java parameter and return type are String. 1112 */ 1113 @Function(schema="javatest", implementor="postgresql_xml", type="xml") 1114 public static String lowLevelXMLEcho(@SQLType("xml") String x) 1115 throws SQLException 1116 { 1117 return x; 1118 } 1119 1120 /** 1121 * Create some XML, pass it to a {@code SELECT ?} prepared statement, 1122 * retrieve it from the result set, and return it via the out-parameter 1123 * result set of this {@code RECORD}-returning function. 1124 */ 1125 @Function(schema="javatest", type="RECORD") 1126 public static boolean xmlInStmtAndRS(ResultSet out) throws SQLException 1127 { 1128 Connection c = DriverManager.getConnection("jdbc:default:connection"); 1129 SQLXML x = c.createSQLXML(); 1130 x.setString("<a/>"); 1131 PreparedStatement ps = c.prepareStatement("SELECT ?"); 1132 ps.setObject(1, x, Types.SQLXML); 1133 ResultSet rs = ps.executeQuery(); 1134 rs.next(); 1135 if ( Types.SQLXML != rs.getMetaData().getColumnType(1) ) 1136 logMessage("WARNING", 1137 "ResultSetMetaData.getColumnType() misreports SQLXML"); 1138 x = rs.getSQLXML(1); 1139 ps.close(); 1140 out.updateObject(1, x); 1141 return true; 1142 } 1143 1144 /** 1145 * Test serialization into the PostgreSQL server encoding by returning 1146 * a text node, optionally wrapped in an element, containing the supplied 1147 * stuff. 1148 *<p> 1149 * The stuff is supplied as a {@code bytea} and a named <em>encoding</em>, 1150 * so it is easy to supply stuff that isn't in the server encoding and see 1151 * what the serializer does with it. 1152 *<p> 1153 * As of this writing, if the <em>stuff</em>, decoded according to 1154 * <em>encoding</em>, contains characters that are not representable in the 1155 * server encoding, the serializers supplied in the JRE will: 1156 *<ul> 1157 *<li>SAX, DOM: replace the character with a numeric character reference if 1158 * the node is wrapped in an element, but not outside of an element; there, 1159 * PL/Java ensures an {@code UnmappableCharacterException} is thrown, as the 1160 * serializer would otherwise silently lose information by replacing the 1161 * character with a {@code ?}. 1162 *<li>StAX: replace the character with a numeric character reference whether 1163 * wrapped in an element or not (outside of an element, this officially 1164 * violates the letter of the XML spec, but does not lose information, and 1165 * is closer to the spirit of SQL/XML with its {@code XML(CONTENT)} type). 1166 *</ul> 1167 * @param stuff Content to be used in the text node 1168 * @param encoding Name of an encoding; stuff will be decoded to Unicode 1169 * according to this encoding, and then serialized into the server encoding, 1170 * where possible. 1171 * @param how Integer specifying which XML API to test, like every other how 1172 * in this class; here the only valid choices are 5 (SAX), 6 (StAX), or 1173 * 7 (DOM). 1174 * @param inElement True if the text node should be wrapped in an element. 1175 * @return The resulting XML content. 1176 */ 1177 @Function(schema="javatest", implementor="postgresql_xml") 1178 public static SQLXML xmlTextNode( 1179 byte[] stuff, String encoding, int how, boolean inElement) 1180 throws Exception 1181 { 1182 if ( 5 > how || how > 7 ) 1183 throw new SQLDataException( 1184 "how must be 5-7 for xmlTextNode", "22003"); 1185 1186 String stuffString = new String(stuff, encoding); 1187 Connection c = DriverManager.getConnection("jdbc:default:connection"); 1188 SQLXML rx = c.createSQLXML(); 1189 1190 switch ( how ) 1191 { 1192 case 5: 1193 SAXResult sxr = rx.setResult(SAXResult.class); 1194 sxr.getHandler().startDocument(); 1195 if ( inElement ) 1196 sxr.getHandler().startElement("", "sax", "sax", 1197 new AttributesImpl()); 1198 sxr.getHandler().characters( 1199 stuffString.toCharArray(), 0, stuffString.length()); 1200 if ( inElement ) 1201 sxr.getHandler().endElement("", "sax", "sax"); 1202 sxr.getHandler().endDocument(); 1203 break; 1204 case 6: 1205 StAXResult stxr = rx.setResult(StAXResult.class); 1206 stxr.getXMLStreamWriter().writeStartDocument(); 1207 if ( inElement ) 1208 stxr.getXMLStreamWriter().writeStartElement("", "stax", ""); 1209 stxr.getXMLStreamWriter().writeCharacters(stuffString); 1210 if ( inElement ) 1211 stxr.getXMLStreamWriter().writeEndElement(); 1212 stxr.getXMLStreamWriter().writeEndDocument(); 1213 break; 1214 case 7: 1215 DOMResult dr = rx.setResult(DOMResult.class); 1216 /* 1217 * Why request features XML and Traversal? 1218 * If the only features requested are from the set 1219 * {Core, XML, LS} and maybe XPath, you get a brain-damaged 1220 * DOMImplementation that violates the org.w3c.dom.DOMImplementation 1221 * contract, as createDocument still tries to make a document 1222 * element even when passed null,null,null when, according to the 1223 * contract, it should not. To get the real implementation that 1224 * works, ask for some feature it supports outside of that core set. 1225 * I don't really need Traversal, but by asking for it, I get what 1226 * I do need. 1227 */ 1228 Document d = DOMImplementationRegistry.newInstance() 1229 .getDOMImplementation("XML Traversal") 1230 .createDocument(null, null, null); 1231 DocumentFragment df = d.createDocumentFragment(); 1232 ( inElement ? df.appendChild(d.createElement("dom")) : df ) 1233 .appendChild(d.createTextNode(stuffString)); 1234 dr.setNode(df); 1235 break; 1236 } 1237 return rx; 1238 } 1239 1240 /** 1241 * Create and leave some number of SQLXML objects unclosed, unused, and 1242 * unreferenced, as a test of reclamation. 1243 * @param howmany Number of SQLXML instances to create. 1244 * @param how If nonzero, the flavor of writing to request on the object 1245 * before abandoning it; if zero, it is left in its initial, writable state. 1246 */ 1247 @Function(schema="javatest") 1248 public static void unclosedSQLXML(int howmany, int how) throws SQLException 1249 { 1250 Connection c = DriverManager.getConnection("jdbc:default:connection"); 1251 while ( howmany --> 0 ) 1252 { 1253 SQLXML sx = c.createSQLXML(); 1254 if ( 0 < how ) 1255 sxToResult(sx, how); 1256 } 1257 } 1258 1259 1260 /** 1261 * Return some instance of {@code Source} for reading an {@code SQLXML} 1262 * object, depending on the parameter {@code how}. 1263 *<p> 1264 * Note that this method always returns a {@code Source}, even for cases 1265 * 1 and 2 (obtaining readable streams directly from the {@code SQLXML} 1266 * object; this method wraps them in {@code Source}), and case 3 1267 * ({@code getString}; this method creates a {@code StringReader} and 1268 * returns it wrapped in a {@code Source}. 1269 */ 1270 private static Source sxToSource(SQLXML sx, int how) throws SQLException 1271 { 1272 switch ( how ) 1273 { 1274 case 1: return new StreamSource(sx.getBinaryStream()); 1275 case 2: return new StreamSource(sx.getCharacterStream()); 1276 case 3: return new StreamSource(new StringReader(sx.getString())); 1277 case 4: return sx.getSource(StreamSource.class); 1278 case 5: return sx.getSource(SAXSource.class); 1279 case 6: return sx.getSource(StAXSource.class); 1280 case 7: return sx.getSource(DOMSource.class); 1281 default: throw new SQLDataException("how should be 1-7", "22003"); 1282 } 1283 } 1284 1285 /** 1286 * Return some instance of {@code Result} for writing an {@code SQLXML} 1287 * object, depending on the parameter {@code how}. 1288 *<p> 1289 * Note that this method always returns a {@code Result}, even for cases 1290 * 1 and 2 (obtaining writable streams directly from the {@code SQLXML} 1291 * object; this method wraps them in {@code Result}), and case 3 1292 * ({@code setString}; this method creates a {@code StringWriter} and 1293 * returns it wrapped in a {@code Result}. 1294 *<p> 1295 * In case 3, it will be necessary, after writing, to get the {@code String} 1296 * from the {@code StringWriter}, and call {@code setString} with it. 1297 */ 1298 private static Result sxToResult(SQLXML sx, int how) throws SQLException 1299 { 1300 switch ( how ) 1301 { 1302 case 1: return new StreamResult(sx.setBinaryStream()); 1303 case 2: return new StreamResult(sx.setCharacterStream()); 1304 case 3: return new StreamResult(new StringWriter()); 1305 case 4: return sx.setResult(StreamResult.class); 1306 case 5: return sx.setResult(SAXResult.class); 1307 case 6: return sx.setResult(StAXResult.class); 1308 case 7: 1309 DOMResult r = sx.setResult(DOMResult.class); 1310 allowFragment(r); // else it'll accept only DOCUMENT form 1311 return r; 1312 default: throw new SQLDataException("how should be 1-7", "22003"); 1313 } 1314 } 1315 1316 /** 1317 * Return some instance of {@code Source} for reading an {@code SQLXML} 1318 * object, depending on the parameter {@code how}, applying any adjustments 1319 * in {@code adjust}. 1320 *<p> 1321 * Allows {@code how} to be zero, meaning to let the implementation choose 1322 * what kind of {@code Source} to present. Otherwise identical to the other 1323 * {@code sxToSource}. 1324 */ 1325 private static Source sxToSource(SQLXML sx, int how, ResultSet adjust) 1326 throws SQLException 1327 { 1328 Source s; 1329 switch ( how ) 1330 { 1331 case 0: s = sx.getSource(Adjusting.XML.Source.class); break; 1332 case 1: 1333 case 2: 1334 case 3: 1335 case 4: 1336 return sxToSource(sx, how); // no adjustments on a StreamSource 1337 case 5: s = sx.getSource(Adjusting.XML.SAXSource.class); break; 1338 case 6: s = sx.getSource(Adjusting.XML.StAXSource.class); break; 1339 case 7: s = sx.getSource(Adjusting.XML.DOMSource.class); break; 1340 default: throw new SQLDataException("how should be 0-7", "22003"); 1341 } 1342 1343 if ( s instanceof Adjusting.XML.Source ) 1344 return applyAdjustments(adjust, (Adjusting.XML.Source<?>)s).get(); 1345 return s; 1346 } 1347 1348 /** 1349 * Return some instance of {@code Result} for writing an {@code SQLXML} 1350 * object, depending on the parameter {@code how} applying any adjustments 1351 * in {@code adjust}. 1352 *<p> 1353 * Allows {@code how} to be zero, meaning to let the implementation choose 1354 * what kind of {@code Result} to present. Otherwise identical to the other 1355 * {@code sxToResult}. 1356 */ 1357 private static Result sxToResult(SQLXML sx, int how, ResultSet adjust) 1358 throws SQLException 1359 { 1360 Result r; 1361 switch ( how ) 1362 { 1363 case 1: // you might wish you could adjust a raw BinaryStream 1364 case 2: // or CharacterStream 1365 case 3: // or String, but you can't. Ask for a StreamResult. 1366 case 5: // SAXResult needs no adjustment 1367 case 6: // StAXResult needs no adjustment 1368 case 7: // DOMResult needs no adjustment 1369 return sxToResult(sx, how); 1370 case 4: r = sx.setResult(Adjusting.XML.StreamResult.class); break; 1371 case 0: r = sx.setResult(Adjusting.XML.Result.class); break; 1372 default: throw new SQLDataException("how should be 0-7", "22003"); 1373 } 1374 1375 if ( r instanceof Adjusting.XML.Result ) 1376 return applyAdjustments(adjust, (Adjusting.XML.Result<?>)r).get(); 1377 return r; 1378 } 1379 1380 /** 1381 * Ensure the closing of whatever method was used to add content to 1382 * an {@code SQLXML} object. 1383 *<p> 1384 * Before a {@code SQLXML} object that has been written to can be used by 1385 * PostgreSQL (returned as a function result, plugged in as a prepared 1386 * statement parameter or into a {@code ResultSet}, etc.), the method used 1387 * for writing it must be "closed" to ensure the writing is complete. 1388 *<p> 1389 * If it is set with {@link SQLXML#setString setString}, nothing more is 1390 * needed; {@code setString} obviously sets the whole value at once. Any 1391 * {@code OutputStream} or {@code Writer} obtained from 1392 * {@link SQLXML#setBinaryStream setBinaryStream} or 1393 * {@link SQLXML#setCharacterStream setCharacterStream}, or from 1394 * {@link SQLXML#setResult setResult}{@code (StreamResult.class)}, has to be 1395 * explicitly closed (a {@link Transformer} does not close its 1396 * {@link Result} when the transformation is complete!). 1397 * Those are cases 1, 2, and 4 here. 1398 *<p> 1399 * Cases 5 ({@code SAXResult}) and 6 ({@code StAXResult}) need no special 1400 * attention; though the {@code Transformer} does not close them, the ones 1401 * returned by this {@code SQLXML} implementation are set up to close 1402 * themselves when the {@code endDocument} event is written. 1403 *<p> 1404 * Case 3 (test of {@code setString} is handled specially here. As this 1405 * class allows testing of all techniques for writing the {@code SQLXML} 1406 * object, and most of those involve a {@code Result}, case 3 is handled 1407 * by also constructing a {@code Result} over a {@link StringWriter} and 1408 * having the content written into that; this method then extracts the 1409 * content from the {@code StringWriter} and passes it to {@code setString}. 1410 * For cases 1 and 2, likewise, the stream obtained with 1411 * {@code getBinaryStream} or {@code getCharacterStream} has been wrapped in 1412 * a {@code Result} for generality in this example. 1413 *<p> 1414 * A typical application will not need the generality seen here; it 1415 * will usually know which technique it is using to write the {@code SQLXML} 1416 * object, and only needs to know how to close that if it needs closing. 1417 * @param r The {@code Result} onto which writing was done. 1418 * @param sx The {@code SQLXML} object being written. 1419 * @param how The integer used in this example class to select which method 1420 * of writing the {@code SQLXML} object was to be tested. 1421 * @return The {@code SQLXML} object {@code sx}, because why not? 1422 */ 1423 public static SQLXML ensureClosed(Result r, SQLXML sx, int how) 1424 throws SQLException 1425 { 1426 switch ( how ) 1427 { 1428 case 1: 1429 case 2: 1430 case 4: 1431 StreamResult sr = (StreamResult)r; 1432 OutputStream os = sr.getOutputStream(); 1433 Writer w = sr.getWriter(); 1434 try 1435 { 1436 if ( null != os ) 1437 os.close(); 1438 if ( null != w ) 1439 w.close(); 1440 } 1441 catch ( IOException ioe ) 1442 { 1443 throw new SQLException( 1444 "Failure closing SQLXML result", "XX000"); 1445 } 1446 break; 1447 case 3: 1448 StringWriter sw = (StringWriter)((StreamResult)r).getWriter(); 1449 String s = sw.toString(); 1450 sx.setString(s); 1451 break; 1452 } 1453 return sx; 1454 } 1455 1456 /** 1457 * Configure a {@code DOMResult} to accept {@code CONTENT} (a/k/a 1458 * document fragment), not only the more restrictive {@code DOCUMENT}. 1459 *<p> 1460 * The other forms of {@code Result} that can be requested will happily 1461 * accept {@code XML(CONTENT)} and not just {@code XML(DOCUMENT)}. 1462 * The {@code DOMResult} is pickier, however: if you first call 1463 * {@link DOMResult#setNode setNode} with a {@code DocumentFragment}, it 1464 * will accept either form, but if you leave the node unset when passing the 1465 * {@code DOMResult} to a transformer, the transformer will default to 1466 * putting a {@code Document} node there, and then it will not accept a 1467 * fragment. 1468 *<p> 1469 * If you need to handle fragments, this method illustrates how to pre-load 1470 * the {@code DOMResult} with an empty {@code DocumentFragment}. Note that 1471 * if you use some XML processing package that supplies its own classes 1472 * implementing DOM nodes, you may need to use a {@code DocumentFragment} 1473 * instance obtained from that package. 1474 */ 1475 public static void allowFragment(DOMResult r) throws SQLException 1476 { 1477 try 1478 { 1479 r.setNode(DocumentBuilderFactory.newInstance() 1480 .newDocumentBuilder().newDocument() 1481 .createDocumentFragment()); 1482 } 1483 catch ( ParserConfigurationException pce ) 1484 { 1485 throw new SQLException("Failed initializing DOMResult", pce); 1486 } 1487 } 1488 1489 private static void shovelBytes(InputStream is, OutputStream os) 1490 throws IOException 1491 { 1492 byte[] b = new byte[8192]; 1493 int got; 1494 while ( -1 != (got = is.read(b)) ) 1495 os.write(b, 0, got); 1496 is.close(); 1497 os.close(); 1498 } 1499 1500 private static void shovelChars(Reader r, Writer w) 1501 throws IOException 1502 { 1503 char[] b = new char[8192]; 1504 int got; 1505 while ( -1 != (got = r.read(b)) ) 1506 w.write(b, 0, got); 1507 r.close(); 1508 w.close(); 1509 } 1510 1511 /** 1512 * Test the MappedUDT (in one direction anyway). 1513 *<p> 1514 * Creates a {@code PassXML} object, the Java class that maps the 1515 * {@code javatest.onexml} composite type, which has one member, of XML 1516 * type. Stores a {@code SQLXML} value in that field of the {@code PassXML} 1517 * object, and passes that to an SQL query that expects and returns 1518 * {@code javatest.onexml}. Retrieves the XML from the value field of the 1519 * {@code PassXML} object created to map the result of the query. 1520 * @return The original XML value, if all goes well. 1521 */ 1522 @Function(schema="javatest", implementor="postgresql_xml") 1523 public static SQLXML xmlFromComposite() throws SQLException 1524 { 1525 Connection c = DriverManager.getConnection("jdbc:default:connection"); 1526 PreparedStatement ps = 1527 c.prepareStatement("SELECT CAST(? AS javatest.onexml)"); 1528 SQLXML x = c.createSQLXML(); 1529 x.setString("<a/>"); 1530 PassXML obj = new PassXML(); 1531 obj.m_value = x; 1532 obj.m_typeName = "javatest.onexml"; 1533 ps.setObject(1, obj); 1534 ResultSet r = ps.executeQuery(); 1535 r.next(); 1536 obj = (PassXML)r.getObject(1); 1537 ps.close(); 1538 return obj.m_value; 1539 } 1540 1541 /* 1542 * Required to serve as a MappedUDT: 1543 */ 1544 /** 1545 * No-arg constructor required of objects that will implement 1546 * {@link SQLData}. 1547 */ 1548 public PassXML() { } 1549 1550 private String m_typeName; 1551 private SQLXML m_value; 1552 1553 @Override 1554 public String getSQLTypeName() { return m_typeName; } 1555 1556 @Override 1557 public void readSQL(SQLInput stream, String typeName) throws SQLException 1558 { 1559 m_typeName = typeName; 1560 m_value = (SQLXML) stream.readObject(); 1561 } 1562 1563 @Override 1564 public void writeSQL(SQLOutput stream) throws SQLException 1565 { 1566 stream.writeSQLXML(m_value); 1567 } 1568 1569 /** 1570 * Class that will proxy methods to another {@code SQLXML} class. 1571 *<p> 1572 * Used for testing the PL/Java can accept input for PostgreSQL from an 1573 * {@code SQLXML} object not of its own implementation (for example, one 1574 * obtained from a different JDBC driver from some other database). 1575 *<p> 1576 * Only the {@code getSource} method is specially treated, to allow 1577 * exercising the various flavors of source. 1578 */ 1579 public static class SQLXMLProxy implements SQLXML 1580 { 1581 private SQLXML m_sx; 1582 private int m_how; 1583 1584 public SQLXMLProxy(SQLXML sx, int how) 1585 { 1586 if ( null == sx ) 1587 throw new NullPointerException("Null SQLXMLProxy target"); 1588 if ( 1 > how || how > 7 || how == 3 ) 1589 throw new IllegalArgumentException( 1590 "\"how\" must be 1, 2, 4, 5, 6, or 7"); 1591 m_sx = sx; 1592 m_how = how; 1593 } 1594 1595 @Override 1596 public void free() throws SQLException { m_sx.free(); } 1597 1598 @Override 1599 public InputStream getBinaryStream() throws SQLException 1600 { 1601 return m_sx.getBinaryStream(); 1602 } 1603 1604 @Override 1605 public OutputStream setBinaryStream() throws SQLException 1606 { 1607 return m_sx.setBinaryStream(); 1608 } 1609 1610 @Override 1611 public Reader getCharacterStream() throws SQLException 1612 { 1613 return m_sx.getCharacterStream(); 1614 } 1615 1616 @Override 1617 public Writer setCharacterStream() throws SQLException 1618 { 1619 return m_sx.setCharacterStream(); 1620 } 1621 1622 @Override 1623 public String getString() throws SQLException 1624 { 1625 return m_sx.getString(); 1626 } 1627 1628 @Override 1629 public void setString(String value) throws SQLException 1630 { 1631 m_sx.setString(value); 1632 } 1633 1634 @Override 1635 @SuppressWarnings("unchecked") // all the fun's when sourceClass is null 1636 public <T extends Source> T getSource(Class<T> sourceClass) 1637 throws SQLException 1638 { 1639 if ( null == sourceClass ) 1640 { 1641 switch ( m_how ) 1642 { 1643 case 1: 1644 return (T)new StreamSource(m_sx.getBinaryStream()); 1645 case 2: 1646 return (T)new StreamSource(m_sx.getCharacterStream()); 1647 case 4: 1648 sourceClass = (Class<T>)StreamSource.class; 1649 break; 1650 case 5: 1651 sourceClass = (Class<T>)SAXSource.class; 1652 break; 1653 case 6: 1654 sourceClass = (Class<T>)StAXSource.class; 1655 break; 1656 case 7: 1657 sourceClass = (Class<T>)DOMSource.class; 1658 break; 1659 } 1660 } 1661 return m_sx.getSource(sourceClass); 1662 } 1663 1664 @Override 1665 public <T extends Result> T setResult(Class<T> resultClass) 1666 throws SQLException 1667 { 1668 return m_sx.setResult(resultClass); 1669 } 1670 } 1671 1672 /** 1673 * Class that will mock an {@code SQLXML} instance, returning only binary or 1674 * character stream data from a byte array or string supplied at 1675 * construction. 1676 */ 1677 public static class SQLXMLMock implements SQLXML 1678 { 1679 private String m_chars; 1680 private byte[] m_bytes; 1681 1682 public SQLXMLMock(String content) 1683 { 1684 if ( null == content ) 1685 throw new NullPointerException("Null SQLXMLMock content"); 1686 m_chars = content; 1687 } 1688 1689 public SQLXMLMock(byte[] content) 1690 { 1691 if ( null == content ) 1692 throw new NullPointerException("Null SQLXMLMock content"); 1693 m_bytes = content; 1694 } 1695 1696 @Override 1697 public void free() throws SQLException { } 1698 1699 @Override 1700 public InputStream getBinaryStream() throws SQLException 1701 { 1702 if ( null != m_bytes ) 1703 return new ByteArrayInputStream(m_bytes); 1704 throw new UnsupportedOperationException( 1705 "SQLXMLMock.getBinaryStream"); 1706 } 1707 1708 @Override 1709 public OutputStream setBinaryStream() throws SQLException 1710 { 1711 throw new UnsupportedOperationException( 1712 "SQLXMLMock.setBinaryStream"); 1713 } 1714 1715 @Override 1716 public Reader getCharacterStream() throws SQLException 1717 { 1718 if ( null != m_chars ) 1719 return new StringReader(m_chars); 1720 throw new UnsupportedOperationException( 1721 "SQLXMLMock.getCharacterStream"); 1722 } 1723 1724 @Override 1725 public Writer setCharacterStream() throws SQLException 1726 { 1727 throw new UnsupportedOperationException( 1728 "SQLXMLMock.setCharacterStream"); 1729 } 1730 1731 @Override 1732 public String getString() throws SQLException 1733 { 1734 if ( null != m_chars ) 1735 return m_chars; 1736 throw new UnsupportedOperationException( 1737 "SQLXMLMock.getString"); 1738 } 1739 1740 @Override 1741 public void setString(String value) throws SQLException 1742 { 1743 throw new UnsupportedOperationException( 1744 "SQLXMLMock.setString"); 1745 } 1746 1747 @Override 1748 @SuppressWarnings("unchecked") // sourceClass==StreamSource is verified 1749 public <T extends Source> T getSource(Class<T> sourceClass) 1750 throws SQLException 1751 { 1752 if ( null != sourceClass && StreamSource.class != sourceClass ) 1753 throw new UnsupportedOperationException( 1754 "SQLXMLMock.getSource(" + sourceClass.getName() + ")"); 1755 if ( null != m_chars ) 1756 return (T) new StreamSource(new StringReader(m_chars)); 1757 return (T) new StreamSource(new ByteArrayInputStream(m_bytes)); 1758 } 1759 1760 @Override 1761 public <T extends Result> T setResult(Class<T> resultClass) 1762 throws SQLException 1763 { 1764 throw new UnsupportedOperationException( 1765 "SQLXMLMock.setResult"); 1766 } 1767 } 1768}