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 =&gt; 1-7) of reading an SQLXML object, and seven
343     * (howout =&gt; 1-7) of returning one.
344     *<p>
345     * If howin =&gt; 0, the XML parameter is simply saved in a static. It can
346     * be read in a subsequent call with sx =&gt; 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 =&gt; (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}