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("&"); 649 } else if(charStr[i] == '<') { 650 specialStr = specialStr.concat("<"); 651 } else if(charStr[i] == '>') { 652 specialStr = specialStr.concat(">"); 653 } else if(charStr[i] == '\'') { 654 specialStr = specialStr.concat("'"); 655 } else if(charStr[i] == '\"') { 656 specialStr = specialStr.concat("""); 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 }