001: /*
002:
003: Derby - Class org.apache.derby.iapi.types.SqlXmlUtil
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.iapi.types;
023:
024: import org.apache.derby.iapi.error.StandardException;
025: import org.apache.derby.iapi.reference.SQLState;
026: import org.apache.derby.iapi.services.io.Formatable;
027: import org.apache.derby.iapi.services.io.StoredFormatIds;
028: import org.apache.derby.iapi.services.sanity.SanityManager;
029:
030: import java.util.Properties;
031: import java.util.ArrayList;
032:
033: import java.io.IOException;
034: import java.io.ObjectOutput;
035: import java.io.ObjectInput;
036: import java.io.StringReader;
037:
038: // -- JDBC 3.0 JAXP API classes.
039:
040: import org.w3c.dom.Attr;
041: import org.w3c.dom.Document;
042: import org.w3c.dom.Element;
043: import org.w3c.dom.Node;
044: import org.w3c.dom.NodeList;
045: import org.w3c.dom.Text;
046:
047: import org.xml.sax.ErrorHandler;
048: import org.xml.sax.InputSource;
049: import org.xml.sax.SAXException;
050: import org.xml.sax.SAXParseException;
051:
052: import javax.xml.parsers.DocumentBuilder;
053: import javax.xml.parsers.DocumentBuilderFactory;
054:
055: import javax.xml.transform.OutputKeys;
056: import javax.xml.transform.TransformerException;
057:
058: // -- Xalan-specific classes.
059:
060: import org.apache.xpath.XPath;
061: import org.apache.xpath.XPathContext;
062: import org.apache.xpath.objects.XObject;
063: import org.apache.xpath.objects.XNodeSet;
064:
065: import org.apache.xml.utils.PrefixResolverDefault;
066:
067: import org.apache.xalan.serialize.DOMSerializer;
068: import org.apache.xalan.serialize.Serializer;
069: import org.apache.xalan.serialize.SerializerFactory;
070: import org.apache.xalan.templates.OutputProperties;
071:
072: /**
073: * This class contains "utility" methods that work with XML-specific
074: * objects that are only available if JAXP and/or Xalan are in
075: * the classpath.
076: *
077: * NOTE: This class is only compiled with JDK 1.4 and higher since
078: * the XML-related classes that it uses (JAXP and Xalan) are not
079: * part of earlier JDKs.
080: *
081: * Having a separate class for this functionality is beneficial
082: * for two reasons:
083: *
084: * 1. Allows us to allocate XML objects and compile an XML
085: * query expression a single time per statement, instead of
086: * having to do it for every row against which the query
087: * is evaluated. An instance of this class is created at
088: * compile time and then passed (using "saved objects")
089: * to the appropriate operator implementation method in
090: * XML.java; see SqlXmlExecutor.java for more about the
091: * role this class plays in "saved object" processing.
092: *
093: * 2. By keeping all XML-specific references in this one class,
094: * we have a single "point of entry" to the XML objects--namely,
095: * the constructor for this class. Thus, if we always make
096: * sure to check for the required XML classes _before_ calling
097: * this class's constructor, we can detect early on whether
098: * some classes (ex. Xalan) are missing, and can throw a friendly
099: * error up front, instead of a ClassNotFoundException somewhere
100: * deeper in the execution codepath. The initial check for the
101: * required XML classes can be found in XML.checkXMLRequirements().
102: *
103: * Note that we don't want to put references to XML-specific
104: * objects directly into XML.java because that class (XML.java) is
105: * instantiated anytime a table with an XML column is referenced.
106: * That would mean that if a user tried to select a non-XML column
107: * (ex. integer) from a table that had at least one XML column in
108: * it, the user would have to have JAXP and Xalan classes in
109: * his/her classpath--which we don't want. Instead, by keeping
110: * all XML-specific objects in this one class, and then only
111: * instantiating this class when an XML operator is used (either
112: * implicitly or explicitly), we make it so that the user is only
113: * required to have XML-specific classes in his/her classpath
114: * _if_ s/he is trying to access or operate on XML values.
115: */
116:
117: public class SqlXmlUtil implements Formatable {
118: // Used to parse a string into an XML value (DOM); checks
119: // the well-formedness of the string while parsing.
120: private DocumentBuilder dBuilder;
121:
122: // Used to serialize an XML value according the standard
123: // XML serialization rules.
124: private Serializer serializer;
125:
126: // Classes used to compile and execute an XPath expression
127: // against Xalan.
128: private XPath query;
129: private XPathContext xpContext;
130:
131: // Used to recompile the XPath expression when this formatable
132: // object is reconstructed. e.g.: SPS
133: private String queryExpr;
134: private String opName;
135: private boolean recompileQuery;
136:
137: /**
138: * Constructor: Initializes objects required for parsing
139: * and serializing XML values. Since most XML operations
140: * that require XML-specific classes perform both parsing
141: * and serialization at some point, we just initialize the
142: * objects up front.
143: */
144: public SqlXmlUtil() throws StandardException {
145: try {
146:
147: /* Note: Use of DocumentBuilderFactory means that we get
148: * whatever XML parser is the "default" for the JVM in
149: * use--and thus, we don't have to hard-code the parser
150: * name, nor do we have to require that the user have a
151: * specific parser in his/her classpath.
152: *
153: * This DocumentBuilder is currently used for parsing
154: * (esp. XMLPARSE), and the SQL/XML spec says that XMLPARSE
155: * should NOT perform validation (SQL/XML[2006], 6.15:
156: * "Perform a non-validating parse of a string to produce
157: * an XML value."). So we disable validation here, and
158: * we also make the parser namespace aware.
159: *
160: * At some point in the future we will probably want to add
161: * support for the XMLVALIDATE function--but until then, user
162: * is unable to validate the XML values s/he inserts.
163: *
164: * Note that, even with validation turned off, XMLPARSE
165: * _will_ still check the well-formedness of the values,
166: * and it _will_ still process DTDs to get default values,
167: * etc--but that's it; no validation errors will be thrown.
168: */
169:
170: DocumentBuilderFactory dBF = null;
171: try {
172:
173: dBF = DocumentBuilderFactory.newInstance();
174:
175: } catch (Throwable e) {
176:
177: /* We assume that if we get an error creating the
178: * DocumentBuilderFactory, it's because there's no
179: * JAXP implementation. This can happen in the
180: * (admittedly unlikely) case where the classpath
181: * contains the JAXP _interfaces_ (ex. via xml-apis.jar)
182: * and the Xalan classes but does not actually
183: * contain a JAXP _implementation_. In that case the
184: * check in XML.checkXMLRequirements() will pass
185: * and this class (SqlXmlUtil) will be instantiated
186: * successfully--which is how we get to this constructor.
187: * But then attempts to create a DocumentBuilderFactory
188: * will fail, bringing us here. Note that we can't
189: * check for a valid JAXP implementation in the
190: * XML.checkXMLRequirements() method because we
191: * always want to allow the XML.java class to be
192: * instantiated, even if the required XML classes
193: * are not present--and that means that it (the
194: * XML class) cannot reference DocumentBuilder nor
195: * any of the JAXP classes directly.
196: */
197: throw StandardException.newException(
198: SQLState.LANG_MISSING_XML_CLASSES, "JAXP");
199:
200: }
201:
202: dBF.setValidating(false);
203: dBF.setNamespaceAware(true);
204:
205: // Load document builder that can be used for parsing XML.
206: dBuilder = dBF.newDocumentBuilder();
207: dBuilder.setErrorHandler(new XMLErrorHandler());
208:
209: // Load serializer for serializing XML into string according
210: // XML serialization rules.
211: loadSerializer();
212:
213: } catch (StandardException se) {
214:
215: // Just rethrow it.
216: throw se;
217:
218: } catch (Throwable t) {
219:
220: /* Must be something caused by JAXP or Xalan; wrap it in a
221: * StandardException and rethrow it. Note: we catch "Throwable"
222: * here to catch as many external errors as possible in order
223: * to minimize the chance of an uncaught JAXP/Xalan error (such
224: * as a NullPointerException) causing Derby to fail in a more
225: * serious way. In particular, an uncaught Java exception
226: * like NPE can result in Derby throwing "ERROR 40XT0: An
227: * internal error was identified by RawStore module" for all
228: * statements on the connection after the failure--which we
229: * clearly don't want. If we catch the error and wrap it,
230: * though, the statement will fail but Derby will continue to
231: * run as normal.
232: */
233: throw StandardException.newException(
234: SQLState.LANG_UNEXPECTED_XML_EXCEPTION, t, t
235: .getMessage());
236:
237: }
238:
239: // At construction time we don't have an XML query expression
240: // to compile. If one is required, we'll load/compile it later.
241: query = null;
242: }
243:
244: /**
245: * Take the received string, which is an XML query expression,
246: * compile it, and store the compiled query locally. Note
247: * that for now, we only support XPath because that's what
248: * Xalan supports.
249: *
250: * @param queryExpr The XPath expression to compile
251: */
252: public void compileXQExpr(String queryExpr, String opName)
253: throws StandardException {
254: try {
255:
256: /* The following XPath constructor compiles the expression
257: * as part of the construction process. We have to pass
258: * in a PrefixResolver object in order to avoid NPEs when
259: * invalid/unknown functions are used, so we just create
260: * a dummy one, which means prefixes will not be resolved
261: * in the query (Xalan will just throw an error if a prefix
262: * is used). In the future we may want to revisit this
263: * to make it easier for users to query based on namespaces.
264: */
265: query = new XPath(queryExpr, null,
266: new PrefixResolverDefault(dBuilder.newDocument()),
267: XPath.SELECT);
268:
269: this .queryExpr = queryExpr;
270: this .opName = opName;
271: this .recompileQuery = false;
272:
273: } catch (Throwable te) {
274:
275: /* Something went wrong during compilation of the
276: * expression; wrap the error and re-throw it.
277: * Note: we catch "Throwable" here to catch as many
278: * Xalan-produced errors as possible in order to
279: * minimize the chance of an uncaught Xalan error
280: * (such as a NullPointerException) causing Derby
281: * to fail in a more serious way. In particular, an
282: * uncaught Java exception like NPE can result in
283: * Derby throwing "ERROR 40XT0: An internal error was
284: * identified by RawStore module" for all statements on
285: * the connection after the failure--which we clearly
286: * don't want. If we catch the error and wrap it,
287: * though, the statement will fail but Derby will
288: * continue to run as normal.
289: */
290: throw StandardException.newException(
291: SQLState.LANG_XML_QUERY_ERROR, te, opName, te
292: .getMessage());
293:
294: }
295: }
296:
297: /**
298: * Take a string representing an XML value and serialize it
299: * according SQL/XML serialization rules. Right now, we perform
300: * this serialization by first parsing the string into a JAXP
301: * Document object, and then applying the serialization semantics
302: * to that Document. That seems a bit inefficient, but neither
303: * Xalan nor JAXP provides a more direct way to do this.
304: *
305: * @param xmlAsText String version of XML on which to perform
306: * serialization.
307: * @return A properly serialized version of xmlAsText.
308: */
309: protected String serializeToString(String xmlAsText)
310: throws Exception {
311: ArrayList aList = new ArrayList();
312:
313: /* The call to dBuilder.parse() is a call to an external
314: * (w.r.t. to Derby) JAXP parser. If the received XML
315: * text references an external DTD, then the JAXP parser
316: * will try to read that external DTD. Thus we wrap the
317: * call to parse inside a privileged action to make sure
318: * that the JAXP parser has the required permissions for
319: * reading the DTD file.
320: */
321: try {
322:
323: final InputSource is = new InputSource(new StringReader(
324: xmlAsText));
325: aList
326: .add(java.security.AccessController
327: .doPrivileged(new java.security.PrivilegedExceptionAction() {
328: public Object run() throws IOException,
329: SAXException {
330: return dBuilder.parse(is);
331: }
332: }));
333:
334: } catch (java.security.PrivilegedActionException pae) {
335:
336: /* Unwrap the privileged exception so that the user can
337: * see what the underlying error is. For example, it could
338: * be an i/o error from parsing the XML value, which can
339: * happen if the XML value references an external DTD file
340: * but the JAXP parser hits an i/o error when trying to read
341: * the DTD. In that case we want to throw the i/o error
342: * itself so that it does not appear as a security exception
343: * to the user.
344: */
345: throw pae.getException();
346:
347: }
348:
349: /* The second argument in the following call is for
350: * catching cases where we have a top-level (parentless)
351: * attribute node--but since we just created the list
352: * with a single Document node, we already we know we
353: * don't have a top-level attribute node in the list,
354: * so we don't have to worry. Hence the "null" here.
355: */
356: return serializeToString(aList, null);
357: }
358:
359: /**
360: * Take an array list (sequence) of XML nodes and/or string values
361: * and serialize that entire list according to SQL/XML serialization
362: * rules, which ultimately point to XML serialization rules as
363: * defined by w3c. As part of that serialization process we have
364: * to first "normalize" the sequence. We do that by iterating through
365: * the list and performing the steps for "sequence normalization" as
366: * defined here:
367: *
368: * http://www.w3.org/TR/xslt-xquery-serialization/#serdm
369: *
370: * This method primarily focuses on taking the steps for normalization;
371: * for the rest of the serialization work, we just make calls on the
372: * DOMSerializer class provided by Xalan.
373: *
374: * @param items List of items to serialize
375: * @param xmlVal XMLDataValue into which the serialized string
376: * returned by this method is ultimately going to be stored.
377: * This is used for keeping track of XML values that represent
378: * sequences having top-level (parentless) attribute nodes.
379: * @return Single string holding the serialized version of the
380: * normalized sequence created from the items in the received
381: * list.
382: */
383: protected String serializeToString(ArrayList items,
384: XMLDataValue xmlVal) throws java.io.IOException {
385: if ((items == null) || (items.size() == 0))
386: // nothing to do; return empty sequence.
387: return "";
388:
389: java.io.StringWriter sWriter = new java.io.StringWriter();
390:
391: // Serializer should have been set by now.
392: if (SanityManager.DEBUG) {
393: SanityManager
394: .ASSERT(serializer != null,
395: "Tried to serialize with uninitialized XML serializer.");
396: }
397:
398: serializer.setWriter(sWriter);
399: DOMSerializer dSer = serializer.asDOMSerializer();
400:
401: int sz = items.size();
402: Object obj = null;
403:
404: /* Step 1: Empty sequence. If we have an empty sequence then we
405: * won't ever enter the for loop and the call to sWriter.toString()
406: * at the end of this method will return an empty string, as
407: * required. Otherwise, for a non-empty sequence our "items"
408: * list already corresponds to "S1".
409: */
410:
411: // Iterate through the list and serialize each item.
412: boolean lastItemWasString = false;
413: for (int i = 0; i < sz; i++) {
414: obj = items.get(i);
415: // if it's a string, then this corresponds to some atomic
416: // value, so just echo the string as it is.
417: if (obj instanceof String) {
418: /* Step 2: Atomic values. If "obj" is a string then it
419: * corresponds to some atomic value whose "lexical
420: * representation" is obj. So we just take that.
421: */
422:
423: if (lastItemWasString) {
424: /* Step 3: Adjacent strings. If we have multiple adjacent
425: * strings then concatenate them with a single space
426: * between them.
427: */
428: sWriter.write(" ");
429: }
430:
431: /* Step 4: Create a Text node from the adjacent strings.
432: * Since we're just going to serialize the Text node back
433: * into a string, we short-cut this step by skipping the
434: * creation of the Text node and just writing the string
435: * out directly to our serialized stream.
436: */
437: sWriter.write((String) obj);
438: lastItemWasString = true;
439: } else if (obj instanceof Attr) {
440: /* Step 7a: Attribute nodes. If there is an Attribute node
441: * node in the sequence then we have to throw a serialization
442: * error. NOTE: The rules say we also have to throw an error
443: * for Namespace nodes, but JAXP doesn't define a "Namespace"
444: * object per se; it just defines namespace prefixes and URIs
445: * on other Nodes. So we just check for attributes. If we
446: * find one then we take note of the fact that the result has
447: * a parentless attribute node and later, if the user calls
448: * XMLSERIALIZE on the received XMLDataValue we'll throw the
449: * error as required. Note that we currently only get here
450: * for the XMLQUERY operator, which means we're serializing
451: * a result sequence returned from Xalan and we're going to
452: * store the serialized version into a Derby XML value. In
453: * that case the serialization is an internal operation--and
454: * since the user didn't ask for it, we don't want to throw
455: * the serialization error here. If we did, then whenever an
456: * XMLQUERY operation returned a result sequence with a top-
457: * level attribute in it, the user would see a serialization
458: * error. That's not correct since it is technically okay for
459: * the XMLQUERY operation to return a sequence with an attribute
460: * node; it's just not okay for a user to explicitly try to
461: * serialize that sequence. So instead of throwing the error
462: * here, we just take note of the fact that the sequence has
463: * a top-level attribute. Then later, IF the user makes an
464: * explicit call to serialize the sequence, we'll throw the
465: * appropriate error (see XML.XMLSerialize()).
466: */
467: if (xmlVal != null)
468: xmlVal.markAsHavingTopLevelAttr();
469: dSer.serialize((Node) obj);
470: lastItemWasString = false;
471: } else { // We have a Node, so try to serialize it.
472: Node n = (Node) obj;
473: if (n instanceof Text) {
474: /* Step 6: Combine adjacent text nodes into a single
475: * text node. Since we're just going to serialize the
476: * Text node back into a string, we short-cut this step
477: * by skipping the creation of a new Text node and just
478: * writing the text value out directly to our serialized
479: * stream. Step 6 also says that empty text nodes should
480: * be dropped--but if the text node is empty, the call
481: * to getNodeValue() will return an empty string and
482: * thus we've effectively "dropped" the text node from
483: * the serialized result. Note: it'd be cleaner to just
484: * call "serialize()" on the Text node like we do for
485: * all other Nodes, but Xalan doesn't allow that. So
486: * use the getNodeValue() method instead.
487: */
488: sWriter.write(n.getNodeValue());
489: } else {
490: /* Steps 5 and 7b: Copy all non-attribute, non-text
491: * nodes to the "normalized sequence" and then serialize
492: * that normalized sequence. We short-cut this by
493: * just letting Xalan do the serialization for every
494: * Node in the current list of items that wasn't
495: * "serialized" as an atomic value, attribute, or
496: * text node.
497: */
498: dSer.serialize(n);
499: }
500:
501: lastItemWasString = false;
502: }
503: }
504:
505: /* At this point sWriter holds the serialized version of the
506: * normalized sequence that corresponds to the received list
507: * of items. So that's what we return.
508: */
509: sWriter.flush();
510: return sWriter.toString();
511: }
512:
513: /**
514: * Evaluate this object's compiled XML query expression against
515: * the received xmlContext. Then if returnResults is false,
516: * return an empty sequence (ArrayList) if evaluation yields
517: * at least one item and return null if evaluation yields zero
518: * items (the caller can then just check for null to see if the
519: * query returned any items). If returnResults is true, then return
520: * return a sequence (ArrayList) containing all items returned
521: * from evaluation of the expression. This array list can contain
522: * any combination of atomic values and XML nodes; it may also
523: * be empty.
524: *
525: * Assumption here is that the query expression has already been
526: * compiled and is stored in this.query.
527: *
528: * @param xmlContext The XML value against which to evaluate
529: * the stored (compiled) query expression
530: * @param returnResults Whether or not to return the actual
531: * results of the query
532: * @param resultXType The qualified XML type of the result
533: * of evaluating the expression, if returnResults is true.
534: * If the result is a sequence of exactly one Document node
535: * then this will be XML(DOCUMENT(ANY)); else it will be
536: * XML(SEQUENCE). If returnResults is false, this value
537: * is ignored.
538: * @return If returnResults is false then return an empty
539: * ArrayList if evaluation returned at least one item and return
540: * null otherwise. If returnResults is true then return an
541: * array list containing all of the result items and return
542: * the qualified XML type via the resultXType parameter.
543: * @exception Exception thrown on error (and turned into a
544: * StandardException by the caller).
545: */
546: protected ArrayList evalXQExpression(XMLDataValue xmlContext,
547: boolean returnResults, int[] resultXType) throws Exception {
548: // if this object is in an SPS, we need to recompile the query
549: if (recompileQuery) {
550: compileXQExpr(queryExpr, opName);
551: }
552:
553: // Make sure we have a compiled query.
554: if (SanityManager.DEBUG) {
555: SanityManager.ASSERT((query != null)
556: && (query.getExpression() != null),
557: "Failed to locate compiled XML query expression.");
558: }
559:
560: /* Create a DOM node from the xmlContext, since that's how
561: * we feed the context to Xalan. We do this by creating
562: * a Document node using DocumentBuilder, which means that
563: * the serialized form of the context node must be a string
564: * value that is parse-able by DocumentBuilder--i.e. it must
565: * constitute a valid XML document. If that's true then
566: * the context item's qualified type will be DOC_ANY.
567: */
568: if (xmlContext.getXType() != XML.XML_DOC_ANY) {
569: throw StandardException.newException(
570: SQLState.LANG_INVALID_XML_CONTEXT_ITEM,
571: (returnResults ? "XMLQUERY" : "XMLEXISTS"));
572: }
573:
574: Document docNode = null;
575: docNode = dBuilder.parse(new InputSource(new StringReader(
576: xmlContext.getString())));
577:
578: // Evaluate the expresion using Xalan.
579: getXPathContext();
580: xpContext.reset();
581: XObject xOb = query.execute(xpContext, docNode, null);
582:
583: if (!returnResults) {
584: // We don't want to return the actual results, we just
585: // want to know if there was at least one item in the
586: // result sequence.
587: if ((xOb instanceof XNodeSet)
588: && (((XNodeSet) xOb).nodelist().getLength() > 0)) { // If we have a sequence (XNodeSet) of length greater
589: // than zero, then we know that at least one item
590: // "exists" in the result so return a non-null list.
591: return new ArrayList(0);
592: } else if (!(xOb instanceof XNodeSet))
593: // we have a single atomic value, which means the result is
594: // non-empty. So return a non-null list.
595: return new ArrayList(0);
596: else {
597: // return null; caller will take this to mean we have an
598: // an empty sequence.
599: return null;
600: }
601: }
602:
603: // Else process the results.
604: NodeList nodeList = null;
605: int numItems = 0;
606: if (!(xOb instanceof XNodeSet))
607: // then we only have a single (probably atomic) item.
608: numItems = 1;
609: else {
610: nodeList = xOb.nodelist();
611: numItems = nodeList.getLength();
612: }
613:
614: // Return a list of the items contained in the query results.
615: ArrayList itemRefs = new ArrayList();
616: if (nodeList == null)
617: // result is a single, non-node value (ex. it's an atomic number);
618: // in this case, just take the string value.
619: itemRefs.add(xOb.str());
620: else {
621: for (int i = 0; i < numItems; i++)
622: itemRefs.add(nodeList.item(i));
623: }
624:
625: nodeList = null;
626:
627: /* Indicate what kind of XML result value we have. If
628: * we have a sequence of exactly one Document then it
629: * is XMLPARSE-able and so we consider it to be of type
630: * XML_DOC_ANY (which means we can store it in a Derby
631: * XML column).
632: */
633: if ((numItems == 1) && (itemRefs.get(0) instanceof Document))
634: resultXType[0] = XML.XML_DOC_ANY;
635: else
636: resultXType[0] = XML.XML_SEQUENCE;
637:
638: return itemRefs;
639: }
640:
641: /* ****
642: * Helper classes and methods.
643: * */
644:
645: /**
646: * Create and return an instance of Xalan's XPathContext
647: * that can be used to compile an XPath expression.
648: */
649: private XPathContext getXPathContext() {
650: if (xpContext == null)
651: xpContext = new XPathContext();
652:
653: return xpContext;
654: }
655:
656: /**
657: * Create an instance of Xalan serializer for the sake of
658: * serializing an XML value according the SQL/XML specification
659: * for serialization.
660: */
661: private void loadSerializer() throws java.io.IOException {
662: java.io.StringWriter sWriter = new java.io.StringWriter();
663:
664: // Set serialization properties.
665: Properties props = OutputProperties
666: .getDefaultMethodProperties("xml");
667:
668: // SQL/XML[2006] 10.15:General Rules:6 says method is "xml".
669: props.setProperty(OutputKeys.METHOD, "xml");
670:
671: /* Since the XMLSERIALIZE operator doesn't currently support
672: * the DOCUMENT nor CONTENT keywords, SQL/XML spec says that
673: * the default is CONTENT (6.7:Syntax Rules:2.a). Further,
674: * since the XMLSERIALIZE operator doesn't currently support the
675: * <XML declaration option> syntax, the SQL/XML spec says
676: * that the default for that option is "Unknown" (6.7:General
677: * Rules:2.f). Put those together and that in turn means that
678: * the value of "OMIT XML DECLARATION" must be "Yes", as
679: * stated in section 10.15:General Rules:8.c. SO, that's what
680: * we set here.
681: *
682: * NOTE: currently the only way to view the contents of an
683: * XML column is by using an explicit XMLSERIALIZE operator.
684: * This means that if an XML document is stored and it
685: * begins with an XML declaration, the user will never be
686: * able to _see_ that declaration after inserting the doc
687: * because, as explained above, our current support for
688: * XMLSERIALIZE dictates that the declaration must be
689: * omitted. Similarly, other transformations that may
690: * occur from serialization (ex. entity replacement,
691: * attribute order, single-to-double quotes, etc)) will
692: * always be in effect for the string returned to the user;
693: * the original form of the XML document, if different
694: * from the serialized version, is not currently retrievable.
695: */
696: props.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
697:
698: // We serialize everything as UTF-8 to match what we
699: // store on disk.
700: props.setProperty(OutputKeys.ENCODING, "UTF-8");
701:
702: // Load the serializer with the correct properties.
703: serializer = SerializerFactory.getSerializer(props);
704: return;
705: }
706:
707: /* ****
708: * Formatable interface implementation
709: * */
710:
711: /**
712: * @see java.io.Externalizable#writeExternal
713: *
714: * @exception IOException on error
715: */
716: public void writeExternal(ObjectOutput out) throws IOException {
717: // query may be null
718: if (query == null) {
719: out.writeBoolean(false);
720: } else {
721: out.writeBoolean(true);
722: out.writeObject(queryExpr);
723: out.writeObject(opName);
724: }
725: }
726:
727: /**
728: * @see java.io.Externalizable#readExternal
729: *
730: * @exception IOException on error
731: * @exception ClassNotFoundException on error
732: */
733: public void readExternal(ObjectInput in) throws IOException,
734: ClassNotFoundException {
735: if (in.readBoolean()) {
736: queryExpr = (String) in.readObject();
737: opName = (String) in.readObject();
738: recompileQuery = true;
739: }
740: }
741:
742: /**
743: * Get the formatID which corresponds to this class.
744: *
745: * @return the formatID of this class
746: */
747: public int getTypeFormatId() {
748: return StoredFormatIds.SQL_XML_UTIL_V01_ID;
749: }
750:
751: /*
752: ** The XMLErrorHandler class is just a generic implementation
753: ** of the ErrorHandler interface. It allows us to catch
754: ** and process XML parsing errors in a graceful manner.
755: */
756: private class XMLErrorHandler implements ErrorHandler {
757: public void error(SAXParseException exception)
758: throws SAXException {
759: throw new SAXException(exception);
760: }
761:
762: public void fatalError(SAXParseException exception)
763: throws SAXException {
764: throw new SAXException(exception);
765: }
766:
767: public void warning(SAXParseException exception)
768: throws SAXException {
769: throw new SAXException(exception);
770: }
771: }
772: }
|