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