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} interface, which writes a 40 * {@code WebRowSet} 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} object to which this {@code WebRowSetXmlWriter} 47 * object will write when its {@code writeXML} method is called. The value 48 * for this field is set with the {@code java.io.Writer} object given 49 * as the second argument to the {@code writeXML} method. 50 */ 51 private transient java.io.Writer writer; 52 53 /** 54 * The {@code java.util.Stack} object that this {@code WebRowSetXmlWriter} 55 * object will use for storing the tags to be used for writing the calling 56 * {@code WebRowSet} 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} object as an XML document 73 * using the given {@code java.io.Writer} object. The XML document 74 * will include the {@code WebRowSet} 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} object that is 79 * referenced in the calling {@code WebRowSet} object's 80 * {@code xmlWriter} field. The {@code XmlWriter.writeXML} 81 * method passes to this method the arguments that were supplied to it. 82 * 83 * @param caller the {@code WebRowSet} object to be written; must 84 * be a rowset for which this {@code WebRowSetXmlWriter} object 85 * is the writer 86 * @param wrt the {@code java.io.Writer} object to which 87 * {@code caller} will be written 88 * @exception SQLException if a database access error occurs or 89 * this {@code WebRowSetXmlWriter} 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} object as an XML document 104 * using the given {@code java.io.OutputStream} object. The XML document 105 * will include the {@code WebRowSet} 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} 110 * 111 * This method is called by the {@code XmlWriter} object that is 112 * referenced in the calling {@code WebRowSet} object's 113 * {@code xmlWriter} field. The {@code XmlWriter.writeXML} 114 * method passes to this method the arguments that were supplied to it. 115 * 116 * @param caller the {@code WebRowSet} object to be written; must 117 * be a rowset for which this {@code WebRowSetXmlWriter} object 118 * is the writer 119 * @param oStream the {@code java.io.OutputStream} object to which 120 * {@code caller} will be written 121 * @throws SQLException if a database access error occurs or 122 * this {@code WebRowSetXmlWriter} 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("&"); 644 } else if(charStr[i] == '<') { 645 specialStr = specialStr.concat("<"); 646 } else if(charStr[i] == '>') { 647 specialStr = specialStr.concat(">"); 648 } else if(charStr[i] == '\'') { 649 specialStr = specialStr.concat("'"); 650 } else if(charStr[i] == '\"') { 651 specialStr = specialStr.concat("""); 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 }