1 /*
   2  * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.rowset.internal;
  27 
  28 import com.sun.rowset.JdbcRowSetResourceBundle;
  29 import java.sql.*;
  30 import javax.sql.*;
  31 import java.io.*;
  32 import java.text.MessageFormat;
  33 import java.util.*;
  34 
  35 import javax.sql.rowset.*;
  36 import javax.sql.rowset.spi.*;
  37 
  38 /**
  39  * An implementation of the <code>XmlWriter</code> interface, which writes a
  40  * <code>WebRowSet</code> object to an output stream as an XML document.
  41  */
  42 
  43 public class WebRowSetXmlWriter implements XmlWriter, Serializable {
  44 
  45     /**
  46      * The <code>java.io.Writer</code> object to which this <code>WebRowSetXmlWriter</code>
  47      * object will write when its <code>writeXML</code> method is called. The value
  48      * for this field is set with the <code>java.io.Writer</code> object given
  49      * as the second argument to the <code>writeXML</code> method.
  50      */
  51     private java.io.Writer writer;
  52 
  53     /**
  54      * The <code>java.util.Stack</code> object that this <code>WebRowSetXmlWriter</code>
  55      * object will use for storing the tags to be used for writing the calling
  56      * <code>WebRowSet</code> object as an XML document.
  57      */
  58     private java.util.Stack<String> stack;
  59 
  60     private  JdbcRowSetResourceBundle resBundle;
  61 
  62     public WebRowSetXmlWriter() {
  63 
  64         try {
  65            resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();
  66         } catch(IOException ioe) {
  67             throw new RuntimeException(ioe);
  68         }
  69     }
  70 
  71     /**
  72      * Writes the given <code>WebRowSet</code> object as an XML document
  73      * using the given <code>java.io.Writer</code> object. The XML document
  74      * will include the <code>WebRowSet</code> object's data, metadata, and
  75      * properties.  If a data value has been updated, that information is also
  76      * included.
  77      * <P>
  78      * This method is called by the <code>XmlWriter</code> object that is
  79      * referenced in the calling <code>WebRowSet</code> object's
  80      * <code>xmlWriter</code> field.  The <code>XmlWriter.writeXML</code>
  81      * method passes to this method the arguments that were supplied to it.
  82      *
  83      * @param caller the <code>WebRowSet</code> object to be written; must
  84      *        be a rowset for which this <code>WebRowSetXmlWriter</code> object
  85      *        is the writer
  86      * @param wrt the <code>java.io.Writer</code> object to which
  87      *        <code>caller</code> will be written
  88      * @exception SQLException if a database access error occurs or
  89      *            this <code>WebRowSetXmlWriter</code> object is not the writer
  90      *            for the given rowset
  91      * @see XmlWriter#writeXML
  92      */
  93     public void writeXML(WebRowSet caller, java.io.Writer wrt)
  94     throws SQLException {
  95 
  96         // create a new stack for tag checking.
  97         stack = new java.util.Stack<String>();
  98         writer = wrt;
  99         writeRowSet(caller);
 100     }
 101 
 102     /**
 103      * Writes the given <code>WebRowSet</code> object as an XML document
 104      * using the given <code>java.io.OutputStream</code> object. The XML document
 105      * will include the <code>WebRowSet</code> object's data, metadata, and
 106      * properties.  If a data value has been updated, that information is also
 107      * included.
 108      * <P>
 109      * Using stream is a faster way than using <code>java.io.Writer<code/>
 110      *
 111      * This method is called by the <code>XmlWriter</code> object that is
 112      * referenced in the calling <code>WebRowSet</code> object's
 113      * <code>xmlWriter</code> field.  The <code>XmlWriter.writeXML</code>
 114      * method passes to this method the arguments that were supplied to it.
 115      *
 116      * @param caller the <code>WebRowSet</code> object to be written; must
 117      *        be a rowset for which this <code>WebRowSetXmlWriter</code> object
 118      *        is the writer
 119      * @param oStream the <code>java.io.OutputStream</code> object to which
 120      *        <code>caller</code> will be written
 121      * @throws SQLException if a database access error occurs or
 122      *            this <code>WebRowSetXmlWriter</code> object is not the writer
 123      *            for the given rowset
 124      * @see XmlWriter#writeXML
 125      */
 126     public void writeXML(WebRowSet caller, java.io.OutputStream oStream)
 127     throws SQLException {
 128 
 129         // create a new stack for tag checking.
 130         stack = new java.util.Stack<String>();
 131         writer = new OutputStreamWriter(oStream);
 132         writeRowSet(caller);
 133     }
 134 
 135     /**
 136      *
 137      *
 138      * @exception SQLException if a database access error occurs
 139      */
 140     private void writeRowSet(WebRowSet caller) throws SQLException {
 141 
 142         try {
 143 
 144             startHeader();
 145 
 146             writeProperties(caller);
 147             writeMetaData(caller);
 148             writeData(caller);
 149 
 150             endHeader();
 151 
 152         } catch (java.io.IOException ex) {
 153             throw new SQLException(MessageFormat.format(resBundle.handleGetObject("wrsxmlwriter.ioex").toString(), ex.getMessage()));
 154         }
 155     }
 156 
 157     private void startHeader() throws java.io.IOException {
 158 
 159         setTag("webRowSet");
 160         writer.write("<?xml version=\"1.0\"?>\n");
 161         writer.write("<webRowSet xmlns=\"http://java.sun.com/xml/ns/jdbc\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
 162         writer.write("xsi:schemaLocation=\"http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/jdbc/webrowset.xsd\">\n");
 163     }
 164 
 165     private void endHeader() throws java.io.IOException {
 166         endTag("webRowSet");
 167     }
 168 
 169     /**
 170      *
 171      *
 172      * @exception SQLException if a database access error occurs
 173      */
 174     private void writeProperties(WebRowSet caller) throws java.io.IOException {
 175 
 176         beginSection("properties");
 177 
 178         try {
 179             propString("command", processSpecialCharacters(caller.getCommand()));
 180             propInteger("concurrency", caller.getConcurrency());
 181             propString("datasource", caller.getDataSourceName());
 182             propBoolean("escape-processing",
 183                     caller.getEscapeProcessing());
 184 
 185             try {
 186                 propInteger("fetch-direction", caller.getFetchDirection());
 187             } catch(SQLException sqle) {
 188                 // it may be the case that fetch direction has not been set
 189                 // fetchDir  == 0
 190                 // in that case it will throw a SQLException.
 191                 // To avoid that catch it here
 192             }
 193 
 194             propInteger("fetch-size", caller.getFetchSize());
 195             propInteger("isolation-level",
 196                     caller.getTransactionIsolation());
 197 
 198             beginSection("key-columns");
 199 
 200             int[] kc = caller.getKeyColumns();
 201             for (int i = 0; kc != null && i < kc.length; i++)
 202                 propInteger("column", kc[i]);
 203 
 204             endSection("key-columns");
 205 
 206             //Changed to beginSection and endSection for maps for proper indentation
 207             beginSection("map");
 208             java.util.Map typeMap = caller.getTypeMap();
 209             if (typeMap != null) {
 210                 Iterator i = typeMap.keySet().iterator();
 211                 Class c;
 212                 String type;
 213                 while (i.hasNext()) {
 214                     type = (String)i.next();
 215                     c = (Class)typeMap.get(type);
 216                     propString("type", type);
 217                     propString("class", c.getName());
 218                 }
 219             }
 220             endSection("map");
 221 
 222             propInteger("max-field-size", caller.getMaxFieldSize());
 223             propInteger("max-rows", caller.getMaxRows());
 224             propInteger("query-timeout", caller.getQueryTimeout());
 225             propBoolean("read-only", caller.isReadOnly());
 226 
 227             int itype = caller.getType();
 228             String strType = "";
 229 
 230             if(itype == 1003) {
 231                 strType = "ResultSet.TYPE_FORWARD_ONLY";
 232             } else if(itype == 1004) {
 233                 strType = "ResultSet.TYPE_SCROLL_INSENSITIVE";
 234             } else if(itype == 1005) {
 235                 strType = "ResultSet.TYPE_SCROLL_SENSITIVE";
 236             }
 237 
 238             propString("rowset-type", strType);
 239 
 240             propBoolean("show-deleted", caller.getShowDeleted());
 241             propString("table-name", caller.getTableName());
 242             propString("url", caller.getUrl());
 243 
 244             beginSection("sync-provider");
 245             // Remove the string after "@xxxx"
 246             // before writing it to the xml file.
 247             String strProviderInstance = (caller.getSyncProvider()).toString();
 248             String strProvider = strProviderInstance.substring(0, (caller.getSyncProvider()).toString().indexOf("@"));
 249 
 250             propString("sync-provider-name", strProvider);
 251             propString("sync-provider-vendor", "Oracle Corporation");
 252             propString("sync-provider-version", "1.0");
 253             propInteger("sync-provider-grade", caller.getSyncProvider().getProviderGrade());
 254             propInteger("data-source-lock", caller.getSyncProvider().getDataSourceLock());
 255 
 256             endSection("sync-provider");
 257 
 258         } catch (SQLException ex) {
 259             throw new java.io.IOException(MessageFormat.format(resBundle.handleGetObject("wrsxmlwriter.sqlex").toString(), ex.getMessage()));
 260         }
 261 
 262         endSection("properties");
 263     }
 264 
 265     /**
 266      *
 267      *
 268      * @exception SQLException if a database access error occurs
 269      */
 270     private void writeMetaData(WebRowSet caller) throws java.io.IOException {
 271         int columnCount;
 272 
 273         beginSection("metadata");
 274 
 275         try {
 276 
 277             ResultSetMetaData rsmd = caller.getMetaData();
 278             columnCount = rsmd.getColumnCount();
 279             propInteger("column-count", columnCount);
 280 
 281             for (int colIndex = 1; colIndex <= columnCount; colIndex++) {
 282                 beginSection("column-definition");
 283 
 284                 propInteger("column-index", colIndex);
 285                 propBoolean("auto-increment", rsmd.isAutoIncrement(colIndex));
 286                 propBoolean("case-sensitive", rsmd.isCaseSensitive(colIndex));
 287                 propBoolean("currency", rsmd.isCurrency(colIndex));
 288                 propInteger("nullable", rsmd.isNullable(colIndex));
 289                 propBoolean("signed", rsmd.isSigned(colIndex));
 290                 propBoolean("searchable", rsmd.isSearchable(colIndex));
 291                 propInteger("column-display-size",rsmd.getColumnDisplaySize(colIndex));
 292                 propString("column-label", rsmd.getColumnLabel(colIndex));
 293                 propString("column-name", rsmd.getColumnName(colIndex));
 294                 propString("schema-name", rsmd.getSchemaName(colIndex));
 295                 propInteger("column-precision", rsmd.getPrecision(colIndex));
 296                 propInteger("column-scale", rsmd.getScale(colIndex));
 297                 propString("table-name", rsmd.getTableName(colIndex));
 298                 propString("catalog-name", rsmd.getCatalogName(colIndex));
 299                 propInteger("column-type", rsmd.getColumnType(colIndex));
 300                 propString("column-type-name", rsmd.getColumnTypeName(colIndex));
 301 
 302                 endSection("column-definition");
 303             }
 304         } catch (SQLException ex) {
 305             throw new java.io.IOException(MessageFormat.format(resBundle.handleGetObject("wrsxmlwriter.sqlex").toString(), ex.getMessage()));
 306         }
 307 
 308         endSection("metadata");
 309     }
 310 
 311     /**
 312      *
 313      *
 314      * @exception SQLException if a database access error occurs
 315      */
 316     private void writeData(WebRowSet caller) throws java.io.IOException {
 317         ResultSet rs;
 318 
 319         try {
 320             ResultSetMetaData rsmd = caller.getMetaData();
 321             int columnCount = rsmd.getColumnCount();
 322             int i;
 323 
 324             beginSection("data");
 325 
 326             caller.beforeFirst();
 327             caller.setShowDeleted(true);
 328             while (caller.next()) {
 329                 if (caller.rowDeleted() && caller.rowInserted()) {
 330                     beginSection("modifyRow");
 331                 } else if (caller.rowDeleted()) {
 332                     beginSection("deleteRow");
 333                 } else if (caller.rowInserted()) {
 334                     beginSection("insertRow");
 335                 } else {
 336                     beginSection("currentRow");
 337                 }
 338 
 339                 for (i = 1; i <= columnCount; i++) {
 340                     if (caller.columnUpdated(i)) {
 341                         rs = caller.getOriginalRow();
 342                         rs.next();
 343                         beginTag("columnValue");
 344                         writeValue(i, (RowSet)rs);
 345                         endTag("columnValue");
 346                         beginTag("updateRow");
 347                         writeValue(i, caller);
 348                         endTag("updateRow");
 349                     } else {
 350                         beginTag("columnValue");
 351                         writeValue(i, caller);
 352                         endTag("columnValue");
 353                     }
 354                 }
 355 
 356                 endSection(); // this is unchecked
 357             }
 358             endSection("data");
 359         } catch (SQLException ex) {
 360             throw new java.io.IOException(MessageFormat.format(resBundle.handleGetObject("wrsxmlwriter.sqlex").toString(), ex.getMessage()));
 361         }
 362     }
 363 
 364     private void writeValue(int idx, RowSet caller) throws java.io.IOException {
 365         try {
 366             int type = caller.getMetaData().getColumnType(idx);
 367 
 368             switch (type) {
 369                 case java.sql.Types.BIT:
 370                 case java.sql.Types.BOOLEAN:
 371                     boolean b = caller.getBoolean(idx);
 372                     if (caller.wasNull())
 373                         writeNull();
 374                     else
 375                         writeBoolean(b);
 376                     break;
 377                 case java.sql.Types.TINYINT:
 378                 case java.sql.Types.SMALLINT:
 379                     short s = caller.getShort(idx);
 380                     if (caller.wasNull())
 381                         writeNull();
 382                     else
 383                         writeShort(s);
 384                     break;
 385                 case java.sql.Types.INTEGER:
 386                     int i = caller.getInt(idx);
 387                     if (caller.wasNull())
 388                         writeNull();
 389                     else
 390                         writeInteger(i);
 391                     break;
 392                 case java.sql.Types.BIGINT:
 393                     long l = caller.getLong(idx);
 394                     if (caller.wasNull())
 395                         writeNull();
 396                     else
 397                         writeLong(l);
 398                     break;
 399                 case java.sql.Types.REAL:
 400                 case java.sql.Types.FLOAT:
 401                     float f = caller.getFloat(idx);
 402                     if (caller.wasNull())
 403                         writeNull();
 404                     else
 405                         writeFloat(f);
 406                     break;
 407                 case java.sql.Types.DOUBLE:
 408                     double d = caller.getDouble(idx);
 409                     if (caller.wasNull())
 410                         writeNull();
 411                     else
 412                         writeDouble(d);
 413                     break;
 414                 case java.sql.Types.NUMERIC:
 415                 case java.sql.Types.DECIMAL:
 416                     writeBigDecimal(caller.getBigDecimal(idx));
 417                     break;
 418                 case java.sql.Types.BINARY:
 419                 case java.sql.Types.VARBINARY:
 420                 case java.sql.Types.LONGVARBINARY:
 421                     break;
 422                 case java.sql.Types.DATE:
 423                     java.sql.Date date = caller.getDate(idx);
 424                     if (caller.wasNull())
 425                         writeNull();
 426                     else
 427                         writeLong(date.getTime());
 428                     break;
 429                 case java.sql.Types.TIME:
 430                     java.sql.Time time = caller.getTime(idx);
 431                     if (caller.wasNull())
 432                         writeNull();
 433                     else
 434                         writeLong(time.getTime());
 435                     break;
 436                 case java.sql.Types.TIMESTAMP:
 437                     java.sql.Timestamp ts = caller.getTimestamp(idx);
 438                     if (caller.wasNull())
 439                         writeNull();
 440                     else
 441                         writeLong(ts.getTime());
 442                     break;
 443                 case java.sql.Types.CHAR:
 444                 case java.sql.Types.VARCHAR:
 445                 case java.sql.Types.LONGVARCHAR:
 446                     writeStringData(caller.getString(idx));
 447                     break;
 448                 default:
 449                     System.out.println(resBundle.handleGetObject("wsrxmlwriter.notproper").toString());
 450                     //Need to take care of BLOB, CLOB, Array, Ref here
 451             }
 452         } catch (SQLException ex) {
 453             throw new java.io.IOException(resBundle.handleGetObject("wrsxmlwriter.failedwrite").toString()+ ex.getMessage());
 454         }
 455     }
 456 
 457     /*
 458      * This begins a new tag with a indent
 459      *
 460      */
 461     private void beginSection(String tag) throws java.io.IOException {
 462         // store the current tag
 463         setTag(tag);
 464 
 465         writeIndent(stack.size());
 466 
 467         // write it out
 468         writer.write("<" + tag + ">\n");
 469     }
 470 
 471     /*
 472      * This closes a tag started by beginTag with a indent
 473      *
 474      */
 475     private void endSection(String tag) throws java.io.IOException {
 476         writeIndent(stack.size());
 477 
 478         String beginTag = getTag();
 479 
 480         if(beginTag.indexOf("webRowSet") != -1) {
 481             beginTag ="webRowSet";
 482         }
 483 
 484         if (tag.equals(beginTag) ) {
 485             // get the current tag and write it out
 486             writer.write("</" + beginTag + ">\n");
 487         } else {
 488             ;
 489         }
 490         writer.flush();
 491     }
 492 
 493     private void endSection() throws java.io.IOException {
 494         writeIndent(stack.size());
 495 
 496         // get the current tag and write it out
 497         String beginTag = getTag();
 498         writer.write("</" + beginTag + ">\n");
 499 
 500         writer.flush();
 501     }
 502 
 503     private void beginTag(String tag) throws java.io.IOException {
 504         // store the current tag
 505         setTag(tag);
 506 
 507         writeIndent(stack.size());
 508 
 509         // write tag out
 510         writer.write("<" + tag + ">");
 511     }
 512 
 513     private void endTag(String tag) throws java.io.IOException {
 514         String beginTag = getTag();
 515         if (tag.equals(beginTag)) {
 516             // get the current tag and write it out
 517             writer.write("</" + beginTag + ">\n");
 518         } else {
 519             ;
 520         }
 521         writer.flush();
 522     }
 523 
 524     private void emptyTag(String tag) throws java.io.IOException {
 525         // write an emptyTag
 526         writer.write("<" + tag + "/>");
 527     }
 528 
 529     private void setTag(String tag) {
 530         // add the tag to stack
 531         stack.push(tag);
 532     }
 533 
 534     private String getTag() {
 535         return (String)stack.pop();
 536     }
 537 
 538     private void writeNull() throws java.io.IOException {
 539         emptyTag("null");
 540     }
 541 
 542     private void writeStringData(String s) throws java.io.IOException {
 543         if (s == null) {
 544             writeNull();
 545         } else if (s.equals("")) {
 546             writeEmptyString();
 547         } else {
 548 
 549             s = processSpecialCharacters(s);
 550 
 551             writer.write(s);
 552         }
 553     }
 554 
 555     private void writeString(String s) throws java.io.IOException {
 556         if (s != null) {
 557             writer.write(s);
 558         } else  {
 559             writeNull();
 560         }
 561     }
 562 
 563 
 564     private void writeShort(short s) throws java.io.IOException {
 565         writer.write(Short.toString(s));
 566     }
 567 
 568     private void writeLong(long l) throws java.io.IOException {
 569         writer.write(Long.toString(l));
 570     }
 571 
 572     private void writeInteger(int i) throws java.io.IOException {
 573         writer.write(Integer.toString(i));
 574     }
 575 
 576     private void writeBoolean(boolean b) throws java.io.IOException {
 577         writer.write(Boolean.valueOf(b).toString());
 578     }
 579 
 580     private void writeFloat(float f) throws java.io.IOException {
 581         writer.write(Float.toString(f));
 582     }
 583 
 584     private void writeDouble(double d) throws java.io.IOException {
 585         writer.write(Double.toString(d));
 586     }
 587 
 588     private void writeBigDecimal(java.math.BigDecimal bd) throws java.io.IOException {
 589         if (bd != null)
 590             writer.write(bd.toString());
 591         else
 592             emptyTag("null");
 593     }
 594 
 595     private void writeIndent(int tabs) throws java.io.IOException {
 596         // indent...
 597         for (int i = 1; i < tabs; i++) {
 598             writer.write("  ");
 599         }
 600     }
 601 
 602     private void propString(String tag, String s) throws java.io.IOException {
 603         beginTag(tag);
 604         writeString(s);
 605         endTag(tag);
 606     }
 607 
 608     private void propInteger(String tag, int i) throws java.io.IOException {
 609         beginTag(tag);
 610         writeInteger(i);
 611         endTag(tag);
 612     }
 613 
 614     private void propBoolean(String tag, boolean b) throws java.io.IOException {
 615         beginTag(tag);
 616         writeBoolean(b);
 617         endTag(tag);
 618     }
 619 
 620     private void writeEmptyString() throws java.io.IOException {
 621         emptyTag("emptyString");
 622     }
 623     /**
 624      * Purely for code coverage purposes..
 625      */
 626     public boolean writeData(RowSetInternal caller) {
 627         return false;
 628     }
 629 
 630 
 631     /**
 632      * This function has been added for the processing of special characters
 633      * lik <,>,'," and & in the data to be serialized. These have to be taken
 634      * of specifically or else there will be parsing error while trying to read
 635      * the contents of the XML file.
 636      **/
 637 
 638     private String processSpecialCharacters(String s) {
 639 
 640         if(s == null) {
 641             return null;
 642         }
 643         char []charStr = s.toCharArray();
 644         String specialStr = "";
 645 
 646         for(int i = 0; i < charStr.length; i++) {
 647             if(charStr[i] == '&') {
 648                 specialStr = specialStr.concat("&amp;");
 649             } else if(charStr[i] == '<') {
 650                 specialStr = specialStr.concat("&lt;");
 651             } else if(charStr[i] == '>') {
 652                 specialStr = specialStr.concat("&gt;");
 653             } else if(charStr[i] == '\'') {
 654                 specialStr = specialStr.concat("&apos;");
 655             } else if(charStr[i] == '\"') {
 656                 specialStr = specialStr.concat("&quot;");
 657             } else {
 658                 specialStr = specialStr.concat(String.valueOf(charStr[i]));
 659             }
 660         }
 661 
 662         s = specialStr;
 663         return s;
 664     }
 665 
 666 
 667     /**
 668      * This method re populates the resBundle
 669      * during the deserialization process
 670      *
 671      */
 672     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
 673         // Default state initialization happens here
 674         ois.defaultReadObject();
 675         // Initialization of transient Res Bundle happens here .
 676         try {
 677            resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();
 678         } catch(IOException ioe) {
 679             throw new RuntimeException(ioe);
 680         }
 681 
 682     }
 683 
 684     static final long serialVersionUID = 7163134986189677641L;
 685 }