--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.iapi.types.SqlXmlUtil\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.iapi.types;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.io.Formatable;\r
+import org.apache.derby.iapi.services.io.StoredFormatIds;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import java.util.Properties;\r
+import java.util.ArrayList;\r
+\r
+import java.io.IOException;\r
+import java.io.ObjectOutput;\r
+import java.io.ObjectInput;\r
+import java.io.StringReader;\r
+\r
+// -- JDBC 3.0 JAXP API classes.\r
+\r
+import org.w3c.dom.Attr;\r
+import org.w3c.dom.Document;\r
+import org.w3c.dom.Element;\r
+import org.w3c.dom.Node;\r
+import org.w3c.dom.NodeList;\r
+import org.w3c.dom.Text;\r
+\r
+import org.xml.sax.ErrorHandler;\r
+import org.xml.sax.InputSource;\r
+import org.xml.sax.SAXException;\r
+import org.xml.sax.SAXParseException;\r
+\r
+import javax.xml.parsers.DocumentBuilder;\r
+import javax.xml.parsers.DocumentBuilderFactory;\r
+\r
+import javax.xml.transform.OutputKeys;\r
+import javax.xml.transform.TransformerException;\r
+\r
+// -- Xalan-specific classes.\r
+\r
+import org.apache.xpath.XPath;\r
+import org.apache.xpath.XPathContext;\r
+import org.apache.xpath.objects.XObject;\r
+import org.apache.xpath.objects.XNodeSet;\r
+\r
+import org.apache.xml.utils.PrefixResolverDefault;\r
+\r
+import org.apache.xalan.serialize.DOMSerializer;\r
+import org.apache.xalan.serialize.Serializer;\r
+import org.apache.xalan.serialize.SerializerFactory;\r
+import org.apache.xalan.templates.OutputProperties;\r
+\r
+/**\r
+ * This class contains "utility" methods that work with XML-specific\r
+ * objects that are only available if JAXP and/or Xalan are in\r
+ * the classpath.\r
+ *\r
+ * NOTE: This class is only compiled with JDK 1.4 and higher since\r
+ * the XML-related classes that it uses (JAXP and Xalan) are not\r
+ * part of earlier JDKs.\r
+ *\r
+ * Having a separate class for this functionality is beneficial\r
+ * for two reasons:\r
+ *\r
+ * 1. Allows us to allocate XML objects and compile an XML\r
+ * query expression a single time per statement, instead of\r
+ * having to do it for every row against which the query\r
+ * is evaluated. An instance of this class is created at\r
+ * compile time and then passed (using "saved objects")\r
+ * to the appropriate operator implementation method in\r
+ * XML.java; see SqlXmlExecutor.java for more about the\r
+ * role this class plays in "saved object" processing.\r
+ *\r
+ * 2. By keeping all XML-specific references in this one class, \r
+ * we have a single "point of entry" to the XML objects--namely,\r
+ * the constructor for this class. Thus, if we always make\r
+ * sure to check for the required XML classes _before_ calling\r
+ * this class's constructor, we can detect early on whether\r
+ * some classes (ex. Xalan) are missing, and can throw a friendly\r
+ * error up front, instead of a ClassNotFoundException somewhere\r
+ * deeper in the execution codepath. The initial check for the\r
+ * required XML classes can be found in XML.checkXMLRequirements().\r
+ *\r
+ * Note that we don't want to put references to XML-specific\r
+ * objects directly into XML.java because that class (XML.java) is\r
+ * instantiated anytime a table with an XML column is referenced.\r
+ * That would mean that if a user tried to select a non-XML column\r
+ * (ex. integer) from a table that had at least one XML column in\r
+ * it, the user would have to have JAXP and Xalan classes in\r
+ * his/her classpath--which we don't want. Instead, by keeping\r
+ * all XML-specific objects in this one class, and then only\r
+ * instantiating this class when an XML operator is used (either\r
+ * implicitly or explicitly), we make it so that the user is only\r
+ * required to have XML-specific classes in his/her classpath\r
+ * _if_ s/he is trying to access or operate on XML values.\r
+ */\r
+\r
+public class SqlXmlUtil implements Formatable\r
+{\r
+ // Used to parse a string into an XML value (DOM); checks\r
+ // the well-formedness of the string while parsing.\r
+ private DocumentBuilder dBuilder;\r
+\r
+ // Used to serialize an XML value according the standard\r
+ // XML serialization rules.\r
+ private Serializer serializer;\r
+\r
+ // Classes used to compile and execute an XPath expression\r
+ // against Xalan.\r
+ private XPath query;\r
+ private XPathContext xpContext;\r
+\r
+ // Used to recompile the XPath expression when this formatable\r
+ // object is reconstructed. e.g.: SPS \r
+ private String queryExpr;\r
+ private String opName;\r
+ private boolean recompileQuery;\r
+ \r
+ /**\r
+ * Constructor: Initializes objects required for parsing\r
+ * and serializing XML values. Since most XML operations\r
+ * that require XML-specific classes perform both parsing\r
+ * and serialization at some point, we just initialize the\r
+ * objects up front.\r
+ */\r
+ public SqlXmlUtil() throws StandardException\r
+ {\r
+ try {\r
+\r
+ /* Note: Use of DocumentBuilderFactory means that we get\r
+ * whatever XML parser is the "default" for the JVM in\r
+ * use--and thus, we don't have to hard-code the parser\r
+ * name, nor do we have to require that the user have a\r
+ * specific parser in his/her classpath.\r
+ *\r
+ * This DocumentBuilder is currently used for parsing\r
+ * (esp. XMLPARSE), and the SQL/XML spec says that XMLPARSE\r
+ * should NOT perform validation (SQL/XML[2006], 6.15:\r
+ * "Perform a non-validating parse of a string to produce\r
+ * an XML value."). So we disable validation here, and\r
+ * we also make the parser namespace aware.\r
+ *\r
+ * At some point in the future we will probably want to add\r
+ * support for the XMLVALIDATE function--but until then, user\r
+ * is unable to validate the XML values s/he inserts.\r
+ *\r
+ * Note that, even with validation turned off, XMLPARSE\r
+ * _will_ still check the well-formedness of the values,\r
+ * and it _will_ still process DTDs to get default values,\r
+ * etc--but that's it; no validation errors will be thrown.\r
+ */\r
+\r
+ DocumentBuilderFactory dBF = null;\r
+ try {\r
+\r
+ dBF = DocumentBuilderFactory.newInstance();\r
+ //Added by Jeff Huang\r
+ //TODO: FIXIT\r
+ } catch (Throwable e) {\r
+\r
+ /* We assume that if we get an error creating the\r
+ * DocumentBuilderFactory, it's because there's no\r
+ * JAXP implementation. This can happen in the\r
+ * (admittedly unlikely) case where the classpath\r
+ * contains the JAXP _interfaces_ (ex. via xml-apis.jar)\r
+ * and the Xalan classes but does not actually\r
+ * contain a JAXP _implementation_. In that case the\r
+ * check in XML.checkXMLRequirements() will pass\r
+ * and this class (SqlXmlUtil) will be instantiated\r
+ * successfully--which is how we get to this constructor.\r
+ * But then attempts to create a DocumentBuilderFactory\r
+ * will fail, bringing us here. Note that we can't\r
+ * check for a valid JAXP implementation in the\r
+ * XML.checkXMLRequirements() method because we\r
+ * always want to allow the XML.java class to be\r
+ * instantiated, even if the required XML classes\r
+ * are not present--and that means that it (the\r
+ * XML class) cannot reference DocumentBuilder nor\r
+ * any of the JAXP classes directly.\r
+ */\r
+ throw StandardException.newException(\r
+ SQLState.LANG_MISSING_XML_CLASSES, "JAXP");\r
+\r
+ }\r
+\r
+ dBF.setValidating(false);\r
+ dBF.setNamespaceAware(true);\r
+\r
+ // Load document builder that can be used for parsing XML.\r
+ dBuilder = dBF.newDocumentBuilder();\r
+ dBuilder.setErrorHandler(new XMLErrorHandler());\r
+\r
+ // Load serializer for serializing XML into string according\r
+ // XML serialization rules.\r
+ loadSerializer();\r
+\r
+ } catch (StandardException se) {\r
+\r
+ // Just rethrow it.\r
+ throw se;\r
+\r
+ } catch (Throwable t) {\r
+\r
+ /* Must be something caused by JAXP or Xalan; wrap it in a\r
+ * StandardException and rethrow it. Note: we catch "Throwable"\r
+ * here to catch as many external errors as possible in order\r
+ * to minimize the chance of an uncaught JAXP/Xalan error (such\r
+ * as a NullPointerException) causing Derby to fail in a more\r
+ * serious way. In particular, an uncaught Java exception\r
+ * like NPE can result in Derby throwing "ERROR 40XT0: An\r
+ * internal error was identified by RawStore module" for all\r
+ * statements on the connection after the failure--which we\r
+ * clearly don't want. If we catch the error and wrap it,\r
+ * though, the statement will fail but Derby will continue to\r
+ * run as normal.\r
+ */ \r
+ throw StandardException.newException(\r
+ SQLState.LANG_UNEXPECTED_XML_EXCEPTION, t, t.getMessage());\r
+\r
+ }\r
+\r
+ // At construction time we don't have an XML query expression\r
+ // to compile. If one is required, we'll load/compile it later.\r
+ query = null;\r
+ }\r
+\r
+ /**\r
+ * Take the received string, which is an XML query expression,\r
+ * compile it, and store the compiled query locally. Note\r
+ * that for now, we only support XPath because that's what\r
+ * Xalan supports.\r
+ *\r
+ * @param queryExpr The XPath expression to compile\r
+ */\r
+ public void compileXQExpr(String queryExpr, String opName)\r
+ throws StandardException\r
+ {\r
+ try {\r
+\r
+ /* The following XPath constructor compiles the expression\r
+ * as part of the construction process. We have to pass\r
+ * in a PrefixResolver object in order to avoid NPEs when\r
+ * invalid/unknown functions are used, so we just create\r
+ * a dummy one, which means prefixes will not be resolved\r
+ * in the query (Xalan will just throw an error if a prefix\r
+ * is used). In the future we may want to revisit this\r
+ * to make it easier for users to query based on namespaces.\r
+ */\r
+ query = new XPath(queryExpr, null,\r
+ new PrefixResolverDefault(dBuilder.newDocument()),\r
+ XPath.SELECT);\r
+ \r
+ this.queryExpr = queryExpr;\r
+ this.opName = opName;\r
+ this.recompileQuery = false;\r
+\r
+ } catch (Throwable te) {\r
+\r
+ /* Something went wrong during compilation of the\r
+ * expression; wrap the error and re-throw it.\r
+ * Note: we catch "Throwable" here to catch as many\r
+ * Xalan-produced errors as possible in order to\r
+ * minimize the chance of an uncaught Xalan error\r
+ * (such as a NullPointerException) causing Derby\r
+ * to fail in a more serious way. In particular, an\r
+ * uncaught Java exception like NPE can result in\r
+ * Derby throwing "ERROR 40XT0: An internal error was\r
+ * identified by RawStore module" for all statements on\r
+ * the connection after the failure--which we clearly\r
+ * don't want. If we catch the error and wrap it,\r
+ * though, the statement will fail but Derby will\r
+ * continue to run as normal. \r
+ */\r
+ throw StandardException.newException(\r
+ SQLState.LANG_XML_QUERY_ERROR, te, opName, te.getMessage());\r
+\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Take a string representing an XML value and serialize it\r
+ * according SQL/XML serialization rules. Right now, we perform\r
+ * this serialization by first parsing the string into a JAXP\r
+ * Document object, and then applying the serialization semantics\r
+ * to that Document. That seems a bit inefficient, but neither\r
+ * Xalan nor JAXP provides a more direct way to do this.\r
+ *\r
+ * @param xmlAsText String version of XML on which to perform\r
+ * serialization.\r
+ * @return A properly serialized version of xmlAsText.\r
+ */\r
+ protected String serializeToString(String xmlAsText)\r
+ throws Exception\r
+ {\r
+ ArrayList aList = new ArrayList();\r
+\r
+ /* The call to dBuilder.parse() is a call to an external\r
+ * (w.r.t. to Derby) JAXP parser. If the received XML\r
+ * text references an external DTD, then the JAXP parser\r
+ * will try to read that external DTD. Thus we wrap the\r
+ * call to parse inside a privileged action to make sure\r
+ * that the JAXP parser has the required permissions for\r
+ * reading the DTD file.\r
+ */\r
+ try {\r
+\r
+ final InputSource is = new InputSource(new StringReader(xmlAsText));\r
+ aList.add(java.security.AccessController.doPrivileged(\r
+ new java.security.PrivilegedExceptionAction()\r
+ {\r
+ public Object run() throws IOException, SAXException\r
+ {\r
+ return dBuilder.parse(is);\r
+ }\r
+ }));\r
+\r
+ } catch (java.security.PrivilegedActionException pae) {\r
+\r
+ /* Unwrap the privileged exception so that the user can\r
+ * see what the underlying error is. For example, it could\r
+ * be an i/o error from parsing the XML value, which can\r
+ * happen if the XML value references an external DTD file\r
+ * but the JAXP parser hits an i/o error when trying to read\r
+ * the DTD. In that case we want to throw the i/o error\r
+ * itself so that it does not appear as a security exception\r
+ * to the user.\r
+ */\r
+ throw pae.getException();\r
+\r
+ }\r
+\r
+ /* The second argument in the following call is for\r
+ * catching cases where we have a top-level (parentless)\r
+ * attribute node--but since we just created the list\r
+ * with a single Document node, we already we know we\r
+ * don't have a top-level attribute node in the list,\r
+ * so we don't have to worry. Hence the "null" here.\r
+ */\r
+ return serializeToString(aList, null);\r
+ }\r
+\r
+ /**\r
+ * Take an array list (sequence) of XML nodes and/or string values\r
+ * and serialize that entire list according to SQL/XML serialization\r
+ * rules, which ultimately point to XML serialization rules as\r
+ * defined by w3c. As part of that serialization process we have\r
+ * to first "normalize" the sequence. We do that by iterating through\r
+ * the list and performing the steps for "sequence normalization" as\r
+ * defined here:\r
+ *\r
+ * http://www.w3.org/TR/xslt-xquery-serialization/#serdm\r
+ *\r
+ * This method primarily focuses on taking the steps for normalization;\r
+ * for the rest of the serialization work, we just make calls on the\r
+ * DOMSerializer class provided by Xalan.\r
+ *\r
+ * @param items List of items to serialize\r
+ * @param xmlVal XMLDataValue into which the serialized string\r
+ * returned by this method is ultimately going to be stored.\r
+ * This is used for keeping track of XML values that represent\r
+ * sequences having top-level (parentless) attribute nodes.\r
+ * @return Single string holding the serialized version of the\r
+ * normalized sequence created from the items in the received\r
+ * list.\r
+ */\r
+ protected String serializeToString(ArrayList items,\r
+ XMLDataValue xmlVal) throws java.io.IOException\r
+ {\r
+ if ((items == null) || (items.size() == 0))\r
+ // nothing to do; return empty sequence.\r
+ return "";\r
+\r
+ java.io.StringWriter sWriter = new java.io.StringWriter();\r
+\r
+ // Serializer should have been set by now.\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(serializer != null,\r
+ "Tried to serialize with uninitialized XML serializer.");\r
+ }\r
+\r
+ serializer.setWriter(sWriter);\r
+ DOMSerializer dSer = serializer.asDOMSerializer();\r
+\r
+ int sz = items.size();\r
+ Object obj = null;\r
+\r
+ /* Step 1: Empty sequence. If we have an empty sequence then we\r
+ * won't ever enter the for loop and the call to sWriter.toString()\r
+ * at the end of this method will return an empty string, as\r
+ * required. Otherwise, for a non-empty sequence our "items"\r
+ * list already corresponds to "S1".\r
+ */\r
+\r
+ // Iterate through the list and serialize each item.\r
+ boolean lastItemWasString = false;\r
+ for (int i = 0; i < sz; i++)\r
+ {\r
+ obj = items.get(i);\r
+ // if it's a string, then this corresponds to some atomic\r
+ // value, so just echo the string as it is.\r
+ if (obj instanceof String)\r
+ {\r
+ /* Step 2: Atomic values. If "obj" is a string then it\r
+ * corresponds to some atomic value whose "lexical\r
+ * representation" is obj. So we just take that.\r
+ */\r
+\r
+ if (lastItemWasString)\r
+ {\r
+ /* Step 3: Adjacent strings. If we have multiple adjacent\r
+ * strings then concatenate them with a single space\r
+ * between them.\r
+ */\r
+ sWriter.write(" ");\r
+ }\r
+\r
+ /* Step 4: Create a Text node from the adjacent strings.\r
+ * Since we're just going to serialize the Text node back\r
+ * into a string, we short-cut this step by skipping the\r
+ * creation of the Text node and just writing the string\r
+ * out directly to our serialized stream.\r
+ */\r
+ sWriter.write((String)obj);\r
+ lastItemWasString = true;\r
+ }\r
+ else if (obj instanceof Attr)\r
+ {\r
+ /* Step 7a: Attribute nodes. If there is an Attribute node\r
+ * node in the sequence then we have to throw a serialization\r
+ * error. NOTE: The rules say we also have to throw an error\r
+ * for Namespace nodes, but JAXP doesn't define a "Namespace"\r
+ * object per se; it just defines namespace prefixes and URIs\r
+ * on other Nodes. So we just check for attributes. If we\r
+ * find one then we take note of the fact that the result has\r
+ * a parentless attribute node and later, if the user calls\r
+ * XMLSERIALIZE on the received XMLDataValue we'll throw the\r
+ * error as required. Note that we currently only get here\r
+ * for the XMLQUERY operator, which means we're serializing\r
+ * a result sequence returned from Xalan and we're going to\r
+ * store the serialized version into a Derby XML value. In\r
+ * that case the serialization is an internal operation--and\r
+ * since the user didn't ask for it, we don't want to throw\r
+ * the serialization error here. If we did, then whenever an\r
+ * XMLQUERY operation returned a result sequence with a top-\r
+ * level attribute in it, the user would see a serialization\r
+ * error. That's not correct since it is technically okay for\r
+ * the XMLQUERY operation to return a sequence with an attribute\r
+ * node; it's just not okay for a user to explicitly try to\r
+ * serialize that sequence. So instead of throwing the error\r
+ * here, we just take note of the fact that the sequence has\r
+ * a top-level attribute. Then later, IF the user makes an\r
+ * explicit call to serialize the sequence, we'll throw the\r
+ * appropriate error (see XML.XMLSerialize()).\r
+ */\r
+ if (xmlVal != null)\r
+ xmlVal.markAsHavingTopLevelAttr();\r
+ dSer.serialize((Node)obj);\r
+ lastItemWasString = false;\r
+ }\r
+ else\r
+ { // We have a Node, so try to serialize it.\r
+ Node n = (Node)obj;\r
+ if (n instanceof Text)\r
+ {\r
+ /* Step 6: Combine adjacent text nodes into a single\r
+ * text node. Since we're just going to serialize the\r
+ * Text node back into a string, we short-cut this step\r
+ * by skipping the creation of a new Text node and just\r
+ * writing the text value out directly to our serialized\r
+ * stream. Step 6 also says that empty text nodes should\r
+ * be dropped--but if the text node is empty, the call\r
+ * to getNodeValue() will return an empty string and\r
+ * thus we've effectively "dropped" the text node from\r
+ * the serialized result. Note: it'd be cleaner to just\r
+ * call "serialize()" on the Text node like we do for\r
+ * all other Nodes, but Xalan doesn't allow that. So\r
+ * use the getNodeValue() method instead.\r
+ */\r
+ sWriter.write(n.getNodeValue());\r
+ }\r
+ else\r
+ {\r
+ /* Steps 5 and 7b: Copy all non-attribute, non-text\r
+ * nodes to the "normalized sequence" and then serialize\r
+ * that normalized sequence. We short-cut this by\r
+ * just letting Xalan do the serialization for every\r
+ * Node in the current list of items that wasn't\r
+ * "serialized" as an atomic value, attribute, or\r
+ * text node.\r
+ */\r
+ dSer.serialize(n);\r
+ }\r
+\r
+ lastItemWasString = false;\r
+ }\r
+ }\r
+\r
+ /* At this point sWriter holds the serialized version of the\r
+ * normalized sequence that corresponds to the received list\r
+ * of items. So that's what we return.\r
+ */\r
+ sWriter.flush();\r
+ return sWriter.toString();\r
+ }\r
+\r
+ /**\r
+ * Evaluate this object's compiled XML query expression against\r
+ * the received xmlContext. Then if returnResults is false,\r
+ * return an empty sequence (ArrayList) if evaluation yields\r
+ * at least one item and return null if evaluation yields zero\r
+ * items (the caller can then just check for null to see if the\r
+ * query returned any items). If returnResults is true, then return\r
+ * return a sequence (ArrayList) containing all items returned\r
+ * from evaluation of the expression. This array list can contain\r
+ * any combination of atomic values and XML nodes; it may also\r
+ * be empty.\r
+ *\r
+ * Assumption here is that the query expression has already been\r
+ * compiled and is stored in this.query.\r
+ *\r
+ * @param xmlContext The XML value against which to evaluate\r
+ * the stored (compiled) query expression\r
+ * @param returnResults Whether or not to return the actual\r
+ * results of the query\r
+ * @param resultXType The qualified XML type of the result\r
+ * of evaluating the expression, if returnResults is true.\r
+ * If the result is a sequence of exactly one Document node\r
+ * then this will be XML(DOCUMENT(ANY)); else it will be\r
+ * XML(SEQUENCE). If returnResults is false, this value\r
+ * is ignored.\r
+ * @return If returnResults is false then return an empty\r
+ * ArrayList if evaluation returned at least one item and return\r
+ * null otherwise. If returnResults is true then return an\r
+ * array list containing all of the result items and return\r
+ * the qualified XML type via the resultXType parameter.\r
+ * @exception Exception thrown on error (and turned into a\r
+ * StandardException by the caller).\r
+ */\r
+ protected ArrayList evalXQExpression(XMLDataValue xmlContext,\r
+ boolean returnResults, int [] resultXType) throws Exception\r
+ {\r
+ // if this object is in an SPS, we need to recompile the query\r
+ if (recompileQuery)\r
+ {\r
+ compileXQExpr(queryExpr, opName);\r
+ }\r
+\r
+ // Make sure we have a compiled query.\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(\r
+ (query != null) && (query.getExpression() != null),\r
+ "Failed to locate compiled XML query expression.");\r
+ }\r
+\r
+ /* Create a DOM node from the xmlContext, since that's how\r
+ * we feed the context to Xalan. We do this by creating\r
+ * a Document node using DocumentBuilder, which means that\r
+ * the serialized form of the context node must be a string\r
+ * value that is parse-able by DocumentBuilder--i.e. it must\r
+ * constitute a valid XML document. If that's true then\r
+ * the context item's qualified type will be DOC_ANY.\r
+ */\r
+ if (xmlContext.getXType() != XML.XML_DOC_ANY)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_INVALID_XML_CONTEXT_ITEM,\r
+ (returnResults ? "XMLQUERY" : "XMLEXISTS"));\r
+ } \r
+\r
+ Document docNode = null;\r
+ docNode = dBuilder.parse(\r
+ new InputSource(\r
+ new StringReader(xmlContext.getString())));\r
+\r
+ // Evaluate the expresion using Xalan.\r
+ getXPathContext();\r
+ xpContext.reset();\r
+ XObject xOb = query.execute(xpContext, docNode, null);\r
+\r
+ if (!returnResults)\r
+ {\r
+ // We don't want to return the actual results, we just\r
+ // want to know if there was at least one item in the\r
+ // result sequence.\r
+ if ((xOb instanceof XNodeSet) &&\r
+ (((XNodeSet)xOb).nodelist().getLength() > 0))\r
+ { // If we have a sequence (XNodeSet) of length greater\r
+ // than zero, then we know that at least one item\r
+ // "exists" in the result so return a non-null list.\r
+ return new ArrayList(0);\r
+ }\r
+ else if (!(xOb instanceof XNodeSet))\r
+ // we have a single atomic value, which means the result is\r
+ // non-empty. So return a non-null list.\r
+ return new ArrayList(0);\r
+ else {\r
+ // return null; caller will take this to mean we have an\r
+ // an empty sequence.\r
+ return null;\r
+ }\r
+ }\r
+\r
+ // Else process the results.\r
+ NodeList nodeList = null;\r
+ int numItems = 0;\r
+ if (!(xOb instanceof XNodeSet))\r
+ // then we only have a single (probably atomic) item.\r
+ numItems = 1;\r
+ else {\r
+ nodeList = xOb.nodelist();\r
+ numItems = nodeList.getLength();\r
+ }\r
+\r
+ // Return a list of the items contained in the query results.\r
+ ArrayList itemRefs = new ArrayList();\r
+ if (nodeList == null)\r
+ // result is a single, non-node value (ex. it's an atomic number);\r
+ // in this case, just take the string value.\r
+ itemRefs.add(xOb.str());\r
+ else {\r
+ for (int i = 0; i < numItems; i++)\r
+ itemRefs.add(nodeList.item(i));\r
+ }\r
+\r
+ nodeList = null;\r
+\r
+ /* Indicate what kind of XML result value we have. If\r
+ * we have a sequence of exactly one Document then it\r
+ * is XMLPARSE-able and so we consider it to be of type\r
+ * XML_DOC_ANY (which means we can store it in a Derby\r
+ * XML column).\r
+ */\r
+ if ((numItems == 1) && (itemRefs.get(0) instanceof Document))\r
+ resultXType[0] = XML.XML_DOC_ANY;\r
+ else\r
+ resultXType[0] = XML.XML_SEQUENCE;\r
+\r
+ return itemRefs;\r
+ }\r
+\r
+ /* ****\r
+ * Helper classes and methods.\r
+ * */\r
+\r
+ /**\r
+ * Create and return an instance of Xalan's XPathContext\r
+ * that can be used to compile an XPath expression.\r
+ */\r
+ private XPathContext getXPathContext()\r
+ {\r
+ if (xpContext == null)\r
+ xpContext = new XPathContext();\r
+\r
+ return xpContext;\r
+ }\r
+\r
+ /**\r
+ * Create an instance of Xalan serializer for the sake of\r
+ * serializing an XML value according the SQL/XML specification\r
+ * for serialization.\r
+ */\r
+ private void loadSerializer() throws java.io.IOException\r
+ {\r
+ java.io.StringWriter sWriter = new java.io.StringWriter();\r
+\r
+ // Set serialization properties.\r
+ Properties props = OutputProperties.getDefaultMethodProperties("xml");\r
+\r
+ // SQL/XML[2006] 10.15:General Rules:6 says method is "xml".\r
+ props.setProperty(OutputKeys.METHOD, "xml");\r
+\r
+ /* Since the XMLSERIALIZE operator doesn't currently support\r
+ * the DOCUMENT nor CONTENT keywords, SQL/XML spec says that\r
+ * the default is CONTENT (6.7:Syntax Rules:2.a). Further,\r
+ * since the XMLSERIALIZE operator doesn't currently support the\r
+ * <XML declaration option> syntax, the SQL/XML spec says\r
+ * that the default for that option is "Unknown" (6.7:General\r
+ * Rules:2.f). Put those together and that in turn means that\r
+ * the value of "OMIT XML DECLARATION" must be "Yes", as\r
+ * stated in section 10.15:General Rules:8.c. SO, that's what\r
+ * we set here.\r
+ *\r
+ * NOTE: currently the only way to view the contents of an\r
+ * XML column is by using an explicit XMLSERIALIZE operator.\r
+ * This means that if an XML document is stored and it\r
+ * begins with an XML declaration, the user will never be\r
+ * able to _see_ that declaration after inserting the doc\r
+ * because, as explained above, our current support for\r
+ * XMLSERIALIZE dictates that the declaration must be\r
+ * omitted. Similarly, other transformations that may\r
+ * occur from serialization (ex. entity replacement,\r
+ * attribute order, single-to-double quotes, etc)) will\r
+ * always be in effect for the string returned to the user;\r
+ * the original form of the XML document, if different\r
+ * from the serialized version, is not currently retrievable.\r
+ */\r
+ props.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");\r
+\r
+ // We serialize everything as UTF-8 to match what we\r
+ // store on disk.\r
+ props.setProperty(OutputKeys.ENCODING, "UTF-8");\r
+\r
+ // Load the serializer with the correct properties.\r
+ serializer = SerializerFactory.getSerializer(props);\r
+ return;\r
+ }\r
+\r
+ /* ****\r
+ * Formatable interface implementation\r
+ * */\r
+\r
+ /** \r
+ * @see java.io.Externalizable#writeExternal \r
+ * \r
+ * @exception IOException on error\r
+ */\r
+ public void writeExternal(ObjectOutput out) \r
+ throws IOException\r
+ {\r
+ // query may be null\r
+ if (query == null)\r
+ {\r
+ out.writeBoolean(false);\r
+ }\r
+ else\r
+ {\r
+ out.writeBoolean(true);\r
+ out.writeObject(queryExpr);\r
+ out.writeObject(opName);\r
+ }\r
+ }\r
+\r
+ /** \r
+ * @see java.io.Externalizable#readExternal \r
+ *\r
+ * @exception IOException on error\r
+ * @exception ClassNotFoundException on error\r
+ */\r
+ public void readExternal(ObjectInput in) \r
+ throws IOException, ClassNotFoundException\r
+ {\r
+ if (in.readBoolean())\r
+ {\r
+ queryExpr = (String)in.readObject();\r
+ opName = (String)in.readObject();\r
+ recompileQuery = true;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Get the formatID which corresponds to this class.\r
+ *\r
+ * @return the formatID of this class\r
+ */\r
+ public int getTypeFormatId()\r
+ { \r
+ return StoredFormatIds.SQL_XML_UTIL_V01_ID;\r
+ }\r
+\r
+ /*\r
+ ** The XMLErrorHandler class is just a generic implementation\r
+ ** of the ErrorHandler interface. It allows us to catch\r
+ ** and process XML parsing errors in a graceful manner.\r
+ */\r
+ private class XMLErrorHandler implements ErrorHandler\r
+ {\r
+ public void error (SAXParseException exception)\r
+ throws SAXException\r
+ {\r
+ throw new SAXException (exception);\r
+ }\r
+\r
+ public void fatalError (SAXParseException exception)\r
+ throws SAXException\r
+ {\r
+ throw new SAXException (exception);\r
+ }\r
+\r
+ public void warning (SAXParseException exception)\r
+ throws SAXException\r
+ {\r
+ throw new SAXException (exception);\r
+ }\r
+ }\r
+}\r