1 /*
   2  * Copyright (c) 2003, 2012, 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 transient 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<>();
  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<>();
 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             Map<String, Class<?>> typeMap = caller.getTypeMap();
 209             if(typeMap != null) {
 210                 for(Map.Entry<String, Class<?>> mm : typeMap.entrySet()) {
 211                     propString("type", mm.getKey());
 212                     propString("class", mm.getValue().getName());
 213                 }
 214             }
 215             endSection("map");
 216 
 217             propInteger("max-field-size", caller.getMaxFieldSize());
 218             propInteger("max-rows", caller.getMaxRows());
 219             propInteger("query-timeout", caller.getQueryTimeout());
 220             propBoolean("read-only", caller.isReadOnly());
 221 
 222             int itype = caller.getType();
 223             String strType = "";
 224 
 225             if(itype == 1003) {
 226                 strType = "ResultSet.TYPE_FORWARD_ONLY";
 227             } else if(itype == 1004) {
 228                 strType = "ResultSet.TYPE_SCROLL_INSENSITIVE";
 229             } else if(itype == 1005) {
 230                 strType = "ResultSet.TYPE_SCROLL_SENSITIVE";
 231             }
 232 
 233             propString("rowset-type", strType);
 234 
 235             propBoolean("show-deleted", caller.getShowDeleted());
 236             propString("table-name", caller.getTableName());
 237             propString("url", caller.getUrl());
 238 
 239             beginSection("sync-provider");
 240             // Remove the string after "@xxxx"
 241             // before writing it to the xml file.
 242             String strProviderInstance = (caller.getSyncProvider()).toString();
 243             String strProvider = strProviderInstance.substring(0, (caller.getSyncProvider()).toString().indexOf('@'));
 244 
 245             propString("sync-provider-name", strProvider);
 246             propString("sync-provider-vendor", "Oracle Corporation");
 247             propString("sync-provider-version", "1.0");
 248             propInteger("sync-provider-grade", caller.getSyncProvider().getProviderGrade());
 249             propInteger("data-source-lock", caller.getSyncProvider().getDataSourceLock());
 250 
 251             endSection("sync-provider");
 252 
 253         } catch (SQLException ex) {
 254             throw new java.io.IOException(MessageFormat.format(resBundle.handleGetObject("wrsxmlwriter.sqlex").toString(), ex.getMessage()));
 255         }
 256 
 257         endSection("properties");
 258     }
 259 
 260     /**
 261      *
 262      *
 263      * @exception SQLException if a database access error occurs
 264      */
 265     private void writeMetaData(WebRowSet caller) throws java.io.IOException {
 266         int columnCount;
 267 
 268         beginSection("metadata");
 269 
 270         try {
 271 
 272             ResultSetMetaData rsmd = caller.getMetaData();
 273             columnCount = rsmd.getColumnCount();
 274             propInteger("column-count", columnCount);
 275 
 276             for (int colIndex = 1; colIndex <= columnCount; colIndex++) {
 277                 beginSection("column-definition");
 278 
 279                 propInteger("column-index", colIndex);
 280                 propBoolean("auto-increment", rsmd.isAutoIncrement(colIndex));
 281                 propBoolean("case-sensitive", rsmd.isCaseSensitive(colIndex));
 282                 propBoolean("currency", rsmd.isCurrency(colIndex));
 283                 propInteger("nullable", rsmd.isNullable(colIndex));
 284                 propBoolean("signed", rsmd.isSigned(colIndex));
 285                 propBoolean("searchable", rsmd.isSearchable(colIndex));
 286                 propInteger("column-display-size",rsmd.getColumnDisplaySize(colIndex));
 287                 propString("column-label", rsmd.getColumnLabel(colIndex));
 288                 propString("column-name", rsmd.getColumnName(colIndex));
 289                 propString("schema-name", rsmd.getSchemaName(colIndex));
 290                 propInteger("column-precision", rsmd.getPrecision(colIndex));
 291                 propInteger("column-scale", rsmd.getScale(colIndex));
 292                 propString("table-name", rsmd.getTableName(colIndex));
 293                 propString("catalog-name", rsmd.getCatalogName(colIndex));
 294                 propInteger("column-type", rsmd.getColumnType(colIndex));
 295                 propString("column-type-name", rsmd.getColumnTypeName(colIndex));
 296 
 297                 endSection("column-definition");
 298             }
 299         } catch (SQLException ex) {
 300             throw new java.io.IOException(MessageFormat.format(resBundle.handleGetObject("wrsxmlwriter.sqlex").toString(), ex.getMessage()));
 301         }
 302 
 303         endSection("metadata");
 304     }
 305 
 306     /**
 307      *
 308      *
 309      * @exception SQLException if a database access error occurs
 310      */
 311     private void writeData(WebRowSet caller) throws java.io.IOException {
 312         ResultSet rs;
 313 
 314         try {
 315             ResultSetMetaData rsmd = caller.getMetaData();
 316             int columnCount = rsmd.getColumnCount();
 317             int i;
 318 
 319             beginSection("data");
 320 
 321             caller.beforeFirst();
 322             caller.setShowDeleted(true);
 323             while (caller.next()) {
 324                 if (caller.rowDeleted() && caller.rowInserted()) {
 325                     beginSection("modifyRow");
 326                 } else if (caller.rowDeleted()) {
 327                     beginSection("deleteRow");
 328                 } else if (caller.rowInserted()) {
 329                     beginSection("insertRow");
 330                 } else {
 331                     beginSection("currentRow");
 332                 }
 333 
 334                 for (i = 1; i <= columnCount; i++) {
 335                     if (caller.columnUpdated(i)) {
 336                         rs = caller.getOriginalRow();
 337                         rs.next();
 338                         beginTag("columnValue");
 339                         writeValue(i, (RowSet)rs);
 340                         endTag("columnValue");
 341                         beginTag("updateRow");
 342                         writeValue(i, caller);
 343                         endTag("updateRow");
 344                     } else {
 345                         beginTag("columnValue");
 346                         writeValue(i, caller);
 347                         endTag("columnValue");
 348                     }
 349                 }
 350 
 351                 endSection(); // this is unchecked
 352             }
 353             endSection("data");
 354         } catch (SQLException ex) {
 355             throw new java.io.IOException(MessageFormat.format(resBundle.handleGetObject("wrsxmlwriter.sqlex").toString(), ex.getMessage()));
 356         }
 357     }
 358 
 359     private void writeValue(int idx, RowSet caller) throws java.io.IOException {
 360         try {
 361             int type = caller.getMetaData().getColumnType(idx);
 362 
 363             switch (type) {
 364                 case java.sql.Types.BIT:
 365                 case java.sql.Types.BOOLEAN:
 366                     boolean b = caller.getBoolean(idx);
 367                     if (caller.wasNull())
 368                         writeNull();
 369                     else
 370                         writeBoolean(b);
 371                     break;
 372                 case java.sql.Types.TINYINT:
 373                 case java.sql.Types.SMALLINT:
 374                     short s = caller.getShort(idx);
 375                     if (caller.wasNull())
 376                         writeNull();
 377                     else
 378                         writeShort(s);
 379                     break;
 380                 case java.sql.Types.INTEGER:
 381                     int i = caller.getInt(idx);
 382                     if (caller.wasNull())
 383                         writeNull();
 384                     else
 385                         writeInteger(i);
 386                     break;
 387                 case java.sql.Types.BIGINT:
 388                     long l = caller.getLong(idx);
 389                     if (caller.wasNull())
 390                         writeNull();
 391                     else
 392                         writeLong(l);
 393                     break;
 394                 case java.sql.Types.REAL:
 395                 case java.sql.Types.FLOAT:
 396                     float f = caller.getFloat(idx);
 397                     if (caller.wasNull())
 398                         writeNull();
 399                     else
 400                         writeFloat(f);
 401                     break;
 402                 case java.sql.Types.DOUBLE:
 403                     double d = caller.getDouble(idx);
 404                     if (caller.wasNull())
 405                         writeNull();
 406                     else
 407                         writeDouble(d);
 408                     break;
 409                 case java.sql.Types.NUMERIC:
 410                 case java.sql.Types.DECIMAL:
 411                     writeBigDecimal(caller.getBigDecimal(idx));
 412                     break;
 413                 case java.sql.Types.BINARY:
 414                 case java.sql.Types.VARBINARY:
 415                 case java.sql.Types.LONGVARBINARY:
 416                     break;
 417                 case java.sql.Types.DATE:
 418                     java.sql.Date date = caller.getDate(idx);
 419                     if (caller.wasNull())
 420                         writeNull();
 421                     else
 422                         writeLong(date.getTime());
 423                     break;
 424                 case java.sql.Types.TIME:
 425                     java.sql.Time time = caller.getTime(idx);
 426                     if (caller.wasNull())
 427                         writeNull();
 428                     else
 429                         writeLong(time.getTime());
 430                     break;
 431                 case java.sql.Types.TIMESTAMP:
 432                     java.sql.Timestamp ts = caller.getTimestamp(idx);
 433                     if (caller.wasNull())
 434                         writeNull();
 435                     else
 436                         writeLong(ts.getTime());
 437                     break;
 438                 case java.sql.Types.CHAR:
 439                 case java.sql.Types.VARCHAR:
 440                 case java.sql.Types.LONGVARCHAR:
 441                     writeStringData(caller.getString(idx));
 442                     break;
 443                 default:
 444                     System.out.println(resBundle.handleGetObject("wsrxmlwriter.notproper").toString());
 445                     //Need to take care of BLOB, CLOB, Array, Ref here
 446             }
 447         } catch (SQLException ex) {
 448             throw new java.io.IOException(resBundle.handleGetObject("wrsxmlwriter.failedwrite").toString()+ ex.getMessage());
 449         }
 450     }
 451 
 452     /*
 453      * This begins a new tag with a indent
 454      *
 455      */
 456     private void beginSection(String tag) throws java.io.IOException {
 457         // store the current tag
 458         setTag(tag);
 459 
 460         writeIndent(stack.size());
 461 
 462         // write it out
 463         writer.write("<" + tag + ">\n");
 464     }
 465 
 466     /*
 467      * This closes a tag started by beginTag with a indent
 468      *
 469      */
 470     private void endSection(String tag) throws java.io.IOException {
 471         writeIndent(stack.size());
 472 
 473         String beginTag = getTag();
 474 
 475         if(beginTag.indexOf("webRowSet") != -1) {
 476             beginTag ="webRowSet";
 477         }
 478 
 479         if (tag.equals(beginTag) ) {
 480             // get the current tag and write it out
 481             writer.write("</" + beginTag + ">\n");
 482         } else {
 483             ;
 484         }
 485         writer.flush();
 486     }
 487 
 488     private void endSection() throws java.io.IOException {
 489         writeIndent(stack.size());
 490 
 491         // get the current tag and write it out
 492         String beginTag = getTag();
 493         writer.write("</" + beginTag + ">\n");
 494 
 495         writer.flush();
 496     }
 497 
 498     private void beginTag(String tag) throws java.io.IOException {
 499         // store the current tag
 500         setTag(tag);
 501 
 502         writeIndent(stack.size());
 503 
 504         // write tag out
 505         writer.write("<" + tag + ">");
 506     }
 507 
 508     private void endTag(String tag) throws java.io.IOException {
 509         String beginTag = getTag();
 510         if (tag.equals(beginTag)) {
 511             // get the current tag and write it out
 512             writer.write("</" + beginTag + ">\n");
 513         } else {
 514             ;
 515         }
 516         writer.flush();
 517     }
 518 
 519     private void emptyTag(String tag) throws java.io.IOException {
 520         // write an emptyTag
 521         writer.write("<" + tag + "/>");
 522     }
 523 
 524     private void setTag(String tag) {
 525         // add the tag to stack
 526         stack.push(tag);
 527     }
 528 
 529     private String getTag() {
 530         return stack.pop();
 531     }
 532 
 533     private void writeNull() throws java.io.IOException {
 534         emptyTag("null");
 535     }
 536 
 537     private void writeStringData(String s) throws java.io.IOException {
 538         if (s == null) {
 539             writeNull();
 540         } else if (s.equals("")) {
 541             writeEmptyString();
 542         } else {
 543 
 544             s = processSpecialCharacters(s);
 545 
 546             writer.write(s);
 547         }
 548     }
 549 
 550     private void writeString(String s) throws java.io.IOException {
 551         if (s != null) {
 552             writer.write(s);
 553         } else  {
 554             writeNull();
 555         }
 556     }
 557 
 558 
 559     private void writeShort(short s) throws java.io.IOException {
 560         writer.write(Short.toString(s));
 561     }
 562 
 563     private void writeLong(long l) throws java.io.IOException {
 564         writer.write(Long.toString(l));
 565     }
 566 
 567     private void writeInteger(int i) throws java.io.IOException {
 568         writer.write(Integer.toString(i));
 569     }
 570 
 571     private void writeBoolean(boolean b) throws java.io.IOException {
 572         writer.write(Boolean.valueOf(b).toString());
 573     }
 574 
 575     private void writeFloat(float f) throws java.io.IOException {
 576         writer.write(Float.toString(f));
 577     }
 578 
 579     private void writeDouble(double d) throws java.io.IOException {
 580         writer.write(Double.toString(d));
 581     }
 582 
 583     private void writeBigDecimal(java.math.BigDecimal bd) throws java.io.IOException {
 584         if (bd != null)
 585             writer.write(bd.toString());
 586         else
 587             emptyTag("null");
 588     }
 589 
 590     private void writeIndent(int tabs) throws java.io.IOException {
 591         // indent...
 592         for (int i = 1; i < tabs; i++) {
 593             writer.write("  ");
 594         }
 595     }
 596 
 597     private void propString(String tag, String s) throws java.io.IOException {
 598         beginTag(tag);
 599         writeString(s);
 600         endTag(tag);
 601     }
 602 
 603     private void propInteger(String tag, int i) throws java.io.IOException {
 604         beginTag(tag);
 605         writeInteger(i);
 606         endTag(tag);
 607     }
 608 
 609     private void propBoolean(String tag, boolean b) throws java.io.IOException {
 610         beginTag(tag);
 611         writeBoolean(b);
 612         endTag(tag);
 613     }
 614 
 615     private void writeEmptyString() throws java.io.IOException {
 616         emptyTag("emptyString");
 617     }
 618     /**
 619      * Purely for code coverage purposes..
 620      */
 621     public boolean writeData(RowSetInternal caller) {
 622         return false;
 623     }
 624 
 625 
 626     /**
 627      * This function has been added for the processing of special characters
 628      * lik <,>,'," and & in the data to be serialized. These have to be taken
 629      * of specifically or else there will be parsing error while trying to read
 630      * the contents of the XML file.
 631      **/
 632 
 633     private String processSpecialCharacters(String s) {
 634 
 635         if(s == null) {
 636             return null;
 637         }
 638         char []charStr = s.toCharArray();
 639         String specialStr = "";
 640 
 641         for(int i = 0; i < charStr.length; i++) {
 642             if(charStr[i] == '&') {
 643                 specialStr = specialStr.concat("&amp;");
 644             } else if(charStr[i] == '<') {
 645                 specialStr = specialStr.concat("&lt;");
 646             } else if(charStr[i] == '>') {
 647                 specialStr = specialStr.concat("&gt;");
 648             } else if(charStr[i] == '\'') {
 649                 specialStr = specialStr.concat("&apos;");
 650             } else if(charStr[i] == '\"') {
 651                 specialStr = specialStr.concat("&quot;");
 652             } else {
 653                 specialStr = specialStr.concat(String.valueOf(charStr[i]));
 654             }
 655         }
 656 
 657         s = specialStr;
 658         return s;
 659     }
 660 
 661 
 662     /**
 663      * This method re populates the resBundle
 664      * during the deserialization process
 665      *
 666      */
 667     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
 668         // Default state initialization happens here
 669         ois.defaultReadObject();
 670         // Initialization of transient Res Bundle happens here .
 671         try {
 672            resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();
 673         } catch(IOException ioe) {
 674             throw new RuntimeException(ioe);
 675         }
 676 
 677     }
 678 
 679     static final long serialVersionUID = 7163134986189677641L;
 680 }