001: /*
002:
003: Derby - Class org.apache.derby.iapi.types.XML
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:
026: import org.apache.derby.iapi.services.cache.ClassSize;
027: import org.apache.derby.iapi.services.io.ArrayInputStream;
028: import org.apache.derby.iapi.services.io.StoredFormatIds;
029: import org.apache.derby.iapi.services.io.StreamStorable;
030: import org.apache.derby.iapi.services.io.Storable;
031: import org.apache.derby.iapi.services.io.TypedFormat;
032: import org.apache.derby.iapi.services.loader.ClassInspector;
033: import org.apache.derby.iapi.services.sanity.SanityManager;
034:
035: import org.apache.derby.iapi.types.DataValueDescriptor;
036: import org.apache.derby.iapi.types.StringDataValue;
037: import org.apache.derby.iapi.types.BooleanDataValue;
038:
039: import org.apache.derby.iapi.reference.SQLState;
040:
041: import java.sql.ResultSet;
042: import java.sql.SQLException;
043: import java.sql.Types;
044:
045: import java.io.InputStream;
046: import java.io.IOException;
047: import java.io.ObjectOutput;
048: import java.io.ObjectInput;
049: import java.io.StringReader;
050:
051: import java.util.ArrayList;
052:
053: /**
054: * This type implements the XMLDataValue interface and thus is
055: * the type on which all XML related operations are executed.
056: *
057: * The first and simplest XML store implementation is a UTF-8
058: * based one--all XML data is stored on disk as a UTF-8 string,
059: * just like the other Derby string types. In order to make
060: * it possible for smarter XML implementations to exist in
061: * the future, this class always writes an "XML implementation
062: * id" to disk before writing the rest of its data. When
063: * reading the data, the impl id is read first and serves
064: * as an indicator of how the rest of the data should be
065: * read.
066: *
067: * So long as there's only one implementation (UTF-8)
068: * the impl id can be ignored; but when smarter implementations
069: * are written, the impl id will be the key to figuring out
070: * how an XML value should be read, written, and processed.
071: */
072: public class XML extends DataType implements XMLDataValue,
073: StreamStorable {
074: // Id for this implementation. Should be unique
075: // across all XML type implementations.
076: protected static final short UTF8_IMPL_ID = 0;
077:
078: // Guess at how much memory this type will take.
079: private static final int BASE_MEMORY_USAGE = ClassSize
080: .estimateBaseFromCatalog(XML.class);
081:
082: // Some syntax-related constants used to determine
083: // operator behavior.
084: public static final short XQ_PASS_BY_REF = 1;
085: public static final short XQ_PASS_BY_VALUE = 2;
086: public static final short XQ_RETURN_SEQUENCE = 3;
087: public static final short XQ_RETURN_CONTENT = 4;
088: public static final short XQ_EMPTY_ON_EMPTY = 5;
089: public static final short XQ_NULL_ON_EMPTY = 6;
090:
091: /* Per SQL/XML[2006] 4.2.2, there are several different
092: * XML "types" defined through use of primary and secondary
093: * "type modifiers". For Derby we only support two kinds:
094: *
095: * XML(DOCUMENT(ANY)) : A valid and well-formed XML
096: * document as defined by W3C, meaning that there is
097: * exactly one root element node. This is the only
098: * type of XML that can be stored into a Derby XML
099: * column. This is also the type returned by a call
100: * to XMLPARSE since we require the DOCUMENT keyword.
101: *
102: * XML(SEQUENCE): A sequence of items (could be nodes or
103: * atomic values). This is the type returned from an
104: * XMLQUERY operation. Any node that is XML(DOCUMENT(ANY))
105: * is also XML(SEQUENCE). Note that an XML(SEQUENCE)
106: * value is *only* storable into a Derby XML column
107: * if it is also an XML(DOCUMENT(ANY)). See the
108: * normalize method below for the code that enforces
109: * this.
110: */
111: public static final int XML_DOC_ANY = 0;
112: public static final int XML_SEQUENCE = 1;
113:
114: // The fully-qualified type for this XML value.
115: private int xType;
116:
117: // The actual XML data in this implementation is just a simple
118: // string, so this class really just wraps a SQLChar and
119: // defers most calls to the corresponding calls on that
120: // SQLChar. Note that, even though a SQLChar is the
121: // underlying implementation, an XML value is nonetheless
122: // NOT considered comparable nor compatible with any of
123: // Derby string types.
124: private SQLChar xmlStringValue;
125:
126: /*
127: * Status variable used to verify that user's classpath contains
128: * required classes for accessing/operating on XML data values.
129: */
130: private static String xmlReqCheck = null;
131:
132: /*
133: * Whether or not this XML value corresponds to a sequence
134: * that has one or more top-level ("parentless") attribute
135: * nodes. If so then we have to throw an error if the user
136: * attempts to serialize this value, per XML serialization
137: * rules.
138: */
139: private boolean containsTopLevelAttr;
140:
141: /**
142: * Default constructor.
143: */
144: public XML() {
145: xmlStringValue = null;
146: xType = -1;
147: containsTopLevelAttr = false;
148: }
149:
150: /**
151: * Private constructor used for the getClone() method.
152: * Returns a new instance of XML whose fields are clones
153: * of the values received.
154: *
155: * @param val A SQLChar instance to clone and use for
156: * this XML value.
157: * @param xmlType Qualified XML type for "val"
158: * @param seqWithAttr Whether or not "val" corresponds to
159: * sequence with one or more top-level attribute nodes.
160: */
161: private XML(SQLChar val, int xmlType, boolean seqWithAttr) {
162: xmlStringValue = (val == null ? null : (SQLChar) val.getClone());
163: setXType(xmlType);
164: if (seqWithAttr)
165: markAsHavingTopLevelAttr();
166: }
167:
168: /* ****
169: * DataValueDescriptor interface.
170: * */
171:
172: /**
173: * @see DataValueDescriptor#getClone
174: */
175: public DataValueDescriptor getClone() {
176: return new XML(xmlStringValue, getXType(), hasTopLevelAttr());
177: }
178:
179: /**
180: * @see DataValueDescriptor#getNewNull
181: */
182: public DataValueDescriptor getNewNull() {
183: return new XML();
184: }
185:
186: /**
187: * @see DataValueDescriptor#getTypeName
188: */
189: public String getTypeName() {
190: return TypeId.XML_NAME;
191: }
192:
193: /**
194: * @see DataValueDescriptor#typePrecedence
195: */
196: public int typePrecedence() {
197: return TypeId.XML_PRECEDENCE;
198: }
199:
200: /**
201: * @see DataValueDescriptor#getString
202: */
203: public String getString() throws StandardException {
204: return (xmlStringValue == null) ? null : xmlStringValue
205: .getString();
206: }
207:
208: /**
209: * @see DataValueDescriptor#getLength
210: */
211: public int getLength() throws StandardException {
212: return ((xmlStringValue == null) ? 0 : xmlStringValue
213: .getLength());
214: }
215:
216: /**
217: * @see DataValueDescriptor#estimateMemoryUsage
218: */
219: public int estimateMemoryUsage() {
220: int sz = BASE_MEMORY_USAGE;
221: if (xmlStringValue != null)
222: sz += xmlStringValue.estimateMemoryUsage();
223: return sz;
224: }
225:
226: /**
227: * @see DataValueDescriptor#readExternalFromArray
228: */
229: public void readExternalFromArray(ArrayInputStream in)
230: throws IOException {
231: if (xmlStringValue == null)
232: xmlStringValue = new SQLChar();
233:
234: // Read the XML implementation id. Right now there's
235: // only one implementation (UTF-8 based), so we don't
236: // use this value. But if better implementations come
237: // up in the future, we'll have to use this impl id to
238: // figure out how to read the data.
239: in.readShort();
240:
241: // Now just read the XML data as UTF-8.
242: xmlStringValue.readExternalFromArray(in);
243:
244: // If we read it from disk then it must have type
245: // XML_DOC_ANY because that's all we allow to be
246: // written into an XML column.
247: setXType(XML_DOC_ANY);
248: }
249:
250: /**
251: * @see DataType#setFrom
252: */
253: protected void setFrom(DataValueDescriptor theValue)
254: throws StandardException {
255: String strVal = theValue.getString();
256: if (strVal == null) {
257: xmlStringValue = null;
258:
259: // Null is a valid value for DOCUMENT(ANY)
260: setXType(XML_DOC_ANY);
261: return;
262: }
263:
264: // Here we just store the received value locally.
265: if (xmlStringValue == null)
266: xmlStringValue = new SQLChar();
267: xmlStringValue.setValue(strVal);
268:
269: /*
270: * Assumption is that if theValue is not an XML
271: * value then the caller is aware of whether or
272: * not theValue constitutes a valid XML(DOCUMENT(ANY))
273: * and will behave accordingly (see in particular the
274: * XMLQuery method of this class, which calls the
275: * setValue() method of XMLDataValue which in turn
276: * brings us to this method).
277: */
278: if (theValue instanceof XMLDataValue) {
279: setXType(((XMLDataValue) theValue).getXType());
280: if (((XMLDataValue) theValue).hasTopLevelAttr())
281: markAsHavingTopLevelAttr();
282: }
283: }
284:
285: /**
286: * @see DataValueDescriptor#setValueFromResultSet
287: */
288: public final void setValueFromResultSet(ResultSet resultSet,
289: int colNumber, boolean isNullable) throws SQLException {
290: if (xmlStringValue == null)
291: xmlStringValue = new SQLChar();
292: xmlStringValue.setValue(resultSet.getString(colNumber));
293: }
294:
295: /**
296: * Compare two XML DataValueDescriptors. NOTE: This method
297: * should only be used by the database store for the purpose of
298: * index positioning--comparisons of XML type are not allowed
299: * from the language side of things. That said, all store
300: * wants to do is order the NULLs, so we don't actually
301: * have to do a full comparison. Just return an order
302: * value based on whether or not this XML value and the
303: * other XML value are null. As mentioned in the "compare"
304: * method of DataValueDescriptor, nulls are considered
305: * equal to other nulls and less than all other values.
306: *
307: * An example of when this method might be used is if the
308: * user executed a query like:
309: *
310: * select i from x_table where x_col is not null
311: *
312: * @see DataValueDescriptor#compare
313: */
314: public int compare(DataValueDescriptor other)
315: throws StandardException {
316: if (SanityManager.DEBUG) {
317: SanityManager.ASSERT(other instanceof XMLDataValue,
318: "Store should NOT have tried to compare an XML value "
319: + "with a non-XML value.");
320: }
321:
322: if (isNull()) {
323: if (other.isNull())
324: // both null, so call them 'equal'.
325: return 0;
326: // This XML is 'less than' the other.
327: return -1;
328: }
329:
330: if (other.isNull())
331: // This XML is 'greater than' the other.
332: return 1;
333:
334: // Two non-null values: we shouldn't ever get here,
335: // since that would necessitate a comparsion of XML
336: // values, which isn't allowed.
337: if (SanityManager.DEBUG) {
338: SanityManager
339: .THROWASSERT("Store tried to compare two non-null XML values, "
340: + "which isn't allowed.");
341: }
342: return 0;
343: }
344:
345: /**
346: * Normalization method - this method will always be called when
347: * storing an XML value into an XML column, for example, when
348: * inserting/updating. We always force normalization in this
349: * case because we need to make sure the qualified type of the
350: * value we're trying to store is XML_DOC_ANY--we don't allow
351: * anything else.
352: *
353: * @param desiredType The type to normalize the source column to
354: * @param source The value to normalize
355: *
356: * @exception StandardException Thrown if source is not
357: * XML_DOC_ANY.
358: */
359: public void normalize(DataTypeDescriptor desiredType,
360: DataValueDescriptor source) throws StandardException {
361: if (SanityManager.DEBUG) {
362: SanityManager
363: .ASSERT(
364: source instanceof XMLDataValue,
365: "Tried to store non-XML value into XML column; "
366: + "should have thrown error at compile time.");
367: }
368:
369: if (((XMLDataValue) source).getXType() != XML_DOC_ANY) {
370: throw StandardException
371: .newException(SQLState.LANG_NOT_AN_XML_DOCUMENT);
372: }
373:
374: ((DataValueDescriptor) this ).setValue(source);
375: return;
376:
377: }
378:
379: /* ****
380: * Storable interface, implies Externalizable, TypedFormat
381: */
382:
383: /**
384: * @see TypedFormat#getTypeFormatId
385: *
386: * From the engine's perspective, all XML implementations share
387: * the same format id.
388: */
389: public int getTypeFormatId() {
390: return StoredFormatIds.XML_ID;
391: }
392:
393: /**
394: * @see Storable#isNull
395: */
396: public boolean isNull() {
397: return ((xmlStringValue == null) || xmlStringValue.isNull());
398: }
399:
400: /**
401: * @see Storable#restoreToNull
402: */
403: public void restoreToNull() {
404: if (xmlStringValue != null)
405: xmlStringValue.restoreToNull();
406: }
407:
408: /**
409: * Read an XML value from an input stream.
410: * @param in The stream from which we're reading.
411: */
412: public void readExternal(ObjectInput in) throws IOException {
413: if (xmlStringValue == null)
414: xmlStringValue = new SQLChar();
415:
416: // Read the XML implementation id. Right now there's
417: // only one implementation (UTF-8 based), so we don't
418: // use this value. But if better implementations come
419: // up in the future, we'll have to use this impl id to
420: // figure out how to read the data.
421: in.readShort();
422:
423: // Now just read the XML data as UTF-8.
424: xmlStringValue.readExternal(in);
425:
426: // If we read it from disk then it must have type
427: // XML_DOC_ANY because that's all we allow to be
428: // written into an XML column.
429: setXType(XML_DOC_ANY);
430: }
431:
432: /**
433: * Write an XML value.
434: * @param out The stream to which we're writing.
435: */
436: public void writeExternal(ObjectOutput out) throws IOException {
437: // never called when value is null
438: if (SanityManager.DEBUG)
439: SanityManager.ASSERT(!isNull());
440:
441: // Write out the XML store impl id.
442: out.writeShort(UTF8_IMPL_ID);
443:
444: // Now write out the data.
445: xmlStringValue.writeExternal(out);
446: }
447:
448: /* ****
449: * StreamStorable interface
450: * */
451:
452: /**
453: * @see StreamStorable#returnStream
454: */
455: public InputStream returnStream() {
456: return (xmlStringValue == null) ? null : xmlStringValue
457: .returnStream();
458: }
459:
460: /**
461: * @see StreamStorable#setStream
462: */
463: public void setStream(InputStream newStream) {
464: if (xmlStringValue == null)
465: xmlStringValue = new SQLChar();
466:
467: // The stream that we receive is for an XML data value,
468: // which means it has an XML implementation id stored
469: // at the front (we put it there when we wrote it out).
470: // If we leave that there we'll get a failure when
471: // our underlying SQLChar tries to read from the
472: // stream, because the extra impl id will throw
473: // off the UTF format. So we need to read in (and
474: // ignore) the impl id before using the stream.
475: try {
476: // 2 bytes equal a short, which is what an impl id is.
477: newStream.read();
478: newStream.read();
479: } catch (Exception e) {
480: if (SanityManager.DEBUG)
481: SanityManager.THROWASSERT("Failed to read impl id"
482: + "bytes in setStream.");
483: }
484:
485: // Now go ahead and use the stream.
486: xmlStringValue.setStream(newStream);
487:
488: // If we read it from disk then it must have type
489: // XML_DOC_ANY because that's all we allow to be
490: // written into an XML column.
491: setXType(XML_DOC_ANY);
492: }
493:
494: /**
495: * @see StreamStorable#loadStream
496: */
497: public void loadStream() throws StandardException {
498: getString();
499: }
500:
501: /* ****
502: * XMLDataValue interface.
503: * */
504:
505: /**
506: * Method to parse an XML string and, if it's valid,
507: * store the _serialized_ version locally and then return
508: * this XMLDataValue.
509: *
510: * @param text The string value to check.
511: * @param preserveWS Whether or not to preserve
512: * ignorable whitespace.
513: * @param sqlxUtil Contains SQL/XML objects and util
514: * methods that facilitate execution of XML-related
515: * operations
516: * @return If 'text' constitutes a valid XML document,
517: * it has been stored in this XML value and this XML
518: * value is returned; otherwise, an exception is thrown.
519: * @exception StandardException Thrown on error.
520: */
521: public XMLDataValue XMLParse(String text, boolean preserveWS,
522: SqlXmlUtil sqlxUtil) throws StandardException {
523: try {
524:
525: if (preserveWS) {
526: // Currently the only way a user can view the contents of
527: // an XML value is by explicitly calling XMLSERIALIZE.
528: // So do a serialization now and just store the result,
529: // so that we don't have to re-serialize every time a
530: // call is made to XMLSERIALIZE.
531: text = sqlxUtil.serializeToString(text);
532: } else {
533: // We don't support this yet, so we shouldn't
534: // get here.
535: if (SanityManager.DEBUG)
536: SanityManager
537: .THROWASSERT("Tried to STRIP whitespace "
538: + "but we shouldn't have made it this far");
539: }
540:
541: } catch (Throwable t) {
542: /* Couldn't parse the XML document. Throw a StandardException
543: * with the parse exception (or other error) nested in it.
544: * Note: we catch "Throwable" here to catch as many external
545: * errors as possible in order to minimize the chance of an
546: * uncaught JAXP/Xalan error (such as a NullPointerException)
547: * causing Derby to fail in a more serious way. In particular,
548: * an uncaught Java exception like NPE can result in Derby
549: * throwing "ERROR 40XT0: An internal error was identified by
550: * RawStore module" for all statements on the connection after
551: * the failure--which we clearly don't want. If we catch the
552: * error and wrap it, though, the statement will fail but Derby
553: * will continue to run as normal.
554: */
555: throw StandardException.newException(
556: SQLState.LANG_INVALID_XML_DOCUMENT, t, t
557: .getMessage());
558:
559: }
560:
561: // If we get here, the text is valid XML so go ahead
562: // and load/store it.
563: setXType(XML_DOC_ANY);
564: if (xmlStringValue == null)
565: xmlStringValue = new SQLChar();
566: xmlStringValue.setValue(text);
567: return this ;
568: }
569:
570: /**
571: * The SQL/XML XMLSerialize operator.
572: * Serializes this XML value into a string with a user-specified
573: * character type, and returns that string via the received
574: * StringDataValue (if the received StringDataValue is non-null
575: * and of the correct type; else, a new StringDataValue is
576: * returned).
577: *
578: * @param result The result of a previous call to this method,
579: * null if not called yet.
580: * @param targetType The string type to which we want to serialize.
581: * @param targetWidth The width of the target type.
582: * @return A serialized (to string) version of this XML object,
583: * in the form of a StringDataValue object.
584: * @exception StandardException Thrown on error
585: */
586: public StringDataValue XMLSerialize(StringDataValue result,
587: int targetType, int targetWidth) throws StandardException {
588: if (result == null) {
589: switch (targetType) {
590: case Types.CHAR:
591: result = new SQLChar();
592: break;
593: case Types.VARCHAR:
594: result = new SQLVarchar();
595: break;
596: case Types.LONGVARCHAR:
597: result = new SQLLongvarchar();
598: break;
599: case Types.CLOB:
600: result = new SQLClob();
601: break;
602: default:
603: // Shouldn't ever get here, as this check was performed
604: // at bind time.
605:
606: if (SanityManager.DEBUG) {
607: SanityManager
608: .THROWASSERT("Should NOT have made it to XMLSerialize "
609: + "with a non-string target type: "
610: + targetType);
611: }
612: return null;
613: }
614: }
615:
616: // Else we're reusing a StringDataValue. We only reuse
617: // the result if we're executing the _same_ XMLSERIALIZE
618: // call on multiple rows. That means that all rows
619: // must have the same result type (targetType) and thus
620: // we know that the StringDataValue already has the
621: // correct type. So we're set.
622:
623: if (this .isNull()) {
624: // Attempts to serialize a null XML value lead to a null
625: // result (SQL/XML[2003] section 10.13).
626: result.setToNull();
627: return result;
628: }
629:
630: /* XML serialization rules say that sequence "normalization"
631: * must occur before serialization, and normalization dictates
632: * that a serialization error must be thrown if the XML value
633: * is a sequence with a top-level attribute. We normalized
634: * (and serialized) this XML value when it was first created,
635: * and at that time we took note of whether or not there is
636: * a top-level attribute. So throw the error here if needed.
637: * See SqlXmlUtil.serializeToString() for more on sequence
638: * normalization.
639: */
640: if (this .hasTopLevelAttr()) {
641: throw StandardException
642: .newException(SQLState.LANG_XQUERY_SERIALIZATION_ERROR);
643: }
644:
645: // Get the XML value as a string. For this UTF-8 impl,
646: // we already have it as a UTF-8 string, so just use
647: // that.
648: result.setValue(getString());
649:
650: // Seems wrong to trunc an XML document, as it then becomes non-
651: // well-formed and thus useless. So we throw an error (that's
652: // what the "true" in the next line says).
653: result.setWidth(targetWidth, 0, true);
654: return result;
655: }
656:
657: /**
658: * The SQL/XML XMLExists operator.
659: * Checks to see if evaluation of the query expression contained
660: * within the received util object against this XML value returns
661: * at least one item. NOTE: For now, the query expression must be
662: * XPath only (XQuery not supported) because that's what Xalan
663: * supports.
664: *
665: * @param sqlxUtil Contains SQL/XML objects and util
666: * methods that facilitate execution of XML-related
667: * operations
668: * @return True if evaluation of the query expression stored
669: * in sqlxUtil returns at least one node for this XML value;
670: * unknown if the xml value is NULL; false otherwise.
671: * @exception StandardException Thrown on error
672: */
673: public BooleanDataValue XMLExists(SqlXmlUtil sqlxUtil)
674: throws StandardException {
675: if (this .isNull()) {
676: // if the user specified a context node and that context
677: // is null, result of evaluating the query is null
678: // (per SQL/XML 6.17:General Rules:1.a), which means that we
679: // return "unknown" here (per SQL/XML 8.4:General Rules:2.a).
680: return SQLBoolean.unknownTruthValue();
681: }
682:
683: // Make sure we have a compiled query (and associated XML
684: // objects) to evaluate.
685: if (SanityManager.DEBUG) {
686: SanityManager
687: .ASSERT(sqlxUtil != null,
688: "Tried to evaluate XML xquery, but no XML objects were loaded.");
689: }
690:
691: try {
692:
693: return new SQLBoolean(null != sqlxUtil.evalXQExpression(
694: this , false, new int[1]));
695:
696: } catch (StandardException se) {
697:
698: // Just re-throw it.
699: throw se;
700:
701: } catch (Throwable xe) {
702: /* Failed somewhere during evaluation of the XML query expression;
703: * turn error into a StandardException and throw it. Note: we
704: * catch "Throwable" here to catch as many Xalan-produced errors
705: * as possible in order to minimize the chance of an uncaught Xalan
706: * error (such as a NullPointerException) causing Derby to fail in
707: * a more serious way. In particular, an uncaught Java exception
708: * like NPE can result in Derby throwing "ERROR 40XT0: An internal
709: * error was identified by RawStore module" for all statements on
710: * the connection after the failure--which we clearly don't want.
711: * If we catch the error and wrap it, though, the statement will
712: * fail but Derby will continue to run as normal.
713: */
714: throw StandardException.newException(
715: SQLState.LANG_XML_QUERY_ERROR, xe, "XMLEXISTS", xe
716: .getMessage());
717: }
718: }
719:
720: /**
721: * Evaluate the XML query expression contained within the received
722: * util object against this XML value and store the results into
723: * the received XMLDataValue "result" param (assuming "result" is
724: * non-null; else create a new XMLDataValue).
725: *
726: * @param result The result of a previous call to this method; null
727: * if not called yet.
728: * @param sqlxUtil Contains SQL/XML objects and util methods that
729: * facilitate execution of XML-related operations
730: * @return An XMLDataValue whose content corresponds to the serialized
731: * version of the results from evaluation of the query expression.
732: * Note: this XMLDataValue may not be storable into Derby XML
733: * columns.
734: * @exception Exception thrown on error (and turned into a
735: * StandardException by the caller).
736: */
737: public XMLDataValue XMLQuery(XMLDataValue result,
738: SqlXmlUtil sqlxUtil) throws StandardException {
739: if (this .isNull()) {
740: // if the context is null, we return null,
741: // per SQL/XML[2006] 6.17:GR.1.a.ii.1.
742: if (result == null)
743: result = (XMLDataValue) getNewNull();
744: else
745: result.setToNull();
746: return result;
747: }
748:
749: try {
750:
751: // Return an XML data value whose contents are the
752: // serialized version of the query results.
753: int[] xType = new int[1];
754: ArrayList itemRefs = sqlxUtil.evalXQExpression(this , true,
755: xType);
756:
757: if (result == null)
758: result = new XML();
759: String strResult = sqlxUtil.serializeToString(itemRefs,
760: result);
761: result.setValue(new SQLChar(strResult));
762:
763: // Now that we've set the result value, make sure
764: // to indicate what kind of XML value we have.
765: result.setXType(xType[0]);
766:
767: // And finally we return the query result as an XML value.
768: return result;
769:
770: } catch (StandardException se) {
771:
772: // Just re-throw it.
773: throw se;
774:
775: } catch (Throwable xe) {
776: /* Failed somewhere during evaluation of the XML query expression;
777: * turn error into a StandardException and throw it. Note: we
778: * catch "Throwable" here to catch as many Xalan-produced errors
779: * as possible in order to minimize the chance of an uncaught Xalan
780: * error (such as a NullPointerException) causing Derby to fail in
781: * a more serious way. In particular, an uncaught Java exception
782: * like NPE can result in Derby throwing "ERROR 40XT0: An internal
783: * error was identified by RawStore module" for all statements on
784: * the connection after the failure--which we clearly don't want.
785: * If we catch the error and wrap it, though, the statement will
786: * fail but Derby will continue to run as normal.
787: */
788: throw StandardException.newException(
789: SQLState.LANG_XML_QUERY_ERROR, xe, "XMLQUERY", xe
790: .getMessage());
791: }
792: }
793:
794: /* ****
795: * Helper classes and methods.
796: * */
797:
798: /**
799: * Set this XML value's qualified type.
800: */
801: public void setXType(int xtype) {
802: this .xType = xtype;
803:
804: /* If the target type is XML_DOC_ANY then this XML value
805: * holds a single well-formed Document. So we know that
806: * we do NOT have any top-level attribute nodes. Note: if
807: * xtype is SEQUENCE we don't set "containsTopLevelAttr"
808: * here; assumption is that the caller of this method will
809: * then set the field as appropriate. Ex. see "setFrom()"
810: * in this class.
811: */
812: if (xtype == XML_DOC_ANY)
813: containsTopLevelAttr = false;
814: }
815:
816: /**
817: * Retrieve this XML value's qualified type.
818: */
819: public int getXType() {
820: return xType;
821: }
822:
823: /**
824: * Take note of the fact this XML value represents an XML
825: * sequence that has one or more top-level attribute nodes.
826: */
827: public void markAsHavingTopLevelAttr() {
828: this .containsTopLevelAttr = true;
829: }
830:
831: /**
832: * Return whether or not this XML value represents a sequence
833: * that has one or more top-level attribute nodes.
834: */
835: public boolean hasTopLevelAttr() {
836: return containsTopLevelAttr;
837: }
838:
839: /**
840: * See if the required JAXP and Xalan classes are in the
841: * user's classpath. Assumption is that we will always
842: * call this method before instantiating an instance of
843: * SqlXmlUtil, and thus we will never get a ClassNotFound
844: * exception caused by missing JAXP/Xalan classes. Instead,
845: * if either is missing we should throw an informative
846: * error indicating what the problem is.
847: *
848: * NOTE: This method only does the checks necessary to
849: * allow successful instantiation of the SqlXmlUtil
850: * class. Further checks (esp. the presence of a JAXP
851: * _implementation_ in addition to the JAXP _interfaces_)
852: * are performed in the SqlXmlUtil constructor.
853: *
854: * @exception StandardException thrown if the required
855: * classes cannot be located in the classpath.
856: */
857: public static void checkXMLRequirements() throws StandardException {
858: // Only check once; after that, just re-use the result.
859: if (xmlReqCheck == null) {
860: xmlReqCheck = "";
861:
862: /* If the w3c Document class exists, then we
863: * assume a JAXP implementation is present in
864: * the classpath. If this assumption is incorrect
865: * then we at least know that the JAXP *interface*
866: * exists and thus we'll be able to instantiate
867: * the SqlXmlUtil class. We can then do a check
868: * for an actual JAXP *implementation* from within
869: * the SqlXmlUtil class (see the constructor of
870: * that class).
871: *
872: * Note: The JAXP API and implementation are
873: * provided as part the JVM if it is jdk 1.4 or
874: * greater.
875: */
876: if (!ClassInspector.classIsLoadable("org.w3c.dom.Document"))
877: xmlReqCheck = "JAXP";
878:
879: /* If the XPath class exists, then we assume that our XML
880: * query processor (in this case, Xalan), is present in the
881: * classpath. Note: if JAXP API classes aren't present
882: * then the following check will return false even if the
883: * Xalan classes *are* present; this is because the Xalan
884: * XPath class relies on JAXP, as well. Thus there's no
885: * point in checking for Xalan unless we've already confirmed
886: * that we have the JAXP interfaces.
887: */
888: else if (!ClassInspector
889: .classIsLoadable("org.apache.xpath.XPath"))
890: xmlReqCheck = "Xalan";
891: }
892:
893: if (xmlReqCheck.length() != 0) {
894: throw StandardException.newException(
895: SQLState.LANG_MISSING_XML_CLASSES, xmlReqCheck);
896: }
897:
898: return;
899: }
900:
901: }
|