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 
WebRowSetXmlWriter()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      */
writeXML(WebRowSet caller, java.io.Writer wrt)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      */
writeXML(WebRowSet caller, java.io.OutputStream oStream)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      */
writeRowSet(WebRowSet caller)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 
startHeader()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 
endHeader()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      */
writeProperties(WebRowSet caller)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      */
writeMetaData(WebRowSet caller)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      */
writeData(WebRowSet caller)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 
writeValue(int idx, RowSet caller)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      */
beginSection(String tag)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      */
endSection(String tag)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 
endSection()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 
beginTag(String tag)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 
endTag(String tag)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 
emptyTag(String tag)519     private void emptyTag(String tag) throws java.io.IOException {
520         // write an emptyTag
521         writer.write("<" + tag + "/>");
522     }
523 
setTag(String tag)524     private void setTag(String tag) {
525         // add the tag to stack
526         stack.push(tag);
527     }
528 
getTag()529     private String getTag() {
530         return stack.pop();
531     }
532 
writeNull()533     private void writeNull() throws java.io.IOException {
534         emptyTag("null");
535     }
536 
writeStringData(String s)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 
writeString(String s)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 
writeShort(short s)559     private void writeShort(short s) throws java.io.IOException {
560         writer.write(Short.toString(s));
561     }
562 
writeLong(long l)563     private void writeLong(long l) throws java.io.IOException {
564         writer.write(Long.toString(l));
565     }
566 
writeInteger(int i)567     private void writeInteger(int i) throws java.io.IOException {
568         writer.write(Integer.toString(i));
569     }
570 
writeBoolean(boolean b)571     private void writeBoolean(boolean b) throws java.io.IOException {
572         writer.write(Boolean.valueOf(b).toString());
573     }
574 
writeFloat(float f)575     private void writeFloat(float f) throws java.io.IOException {
576         writer.write(Float.toString(f));
577     }
578 
writeDouble(double d)579     private void writeDouble(double d) throws java.io.IOException {
580         writer.write(Double.toString(d));
581     }
582 
writeBigDecimal(java.math.BigDecimal bd)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 
writeIndent(int tabs)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 
propString(String tag, String s)597     private void propString(String tag, String s) throws java.io.IOException {
598         beginTag(tag);
599         writeString(s);
600         endTag(tag);
601     }
602 
propInteger(String tag, int i)603     private void propInteger(String tag, int i) throws java.io.IOException {
604         beginTag(tag);
605         writeInteger(i);
606         endTag(tag);
607     }
608 
propBoolean(String tag, boolean b)609     private void propBoolean(String tag, boolean b) throws java.io.IOException {
610         beginTag(tag);
611         writeBoolean(b);
612         endTag(tag);
613     }
614 
writeEmptyString()615     private void writeEmptyString() throws java.io.IOException {
616         emptyTag("emptyString");
617     }
618     /**
619      * Purely for code coverage purposes..
620      */
writeData(RowSetInternal caller)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 
processSpecialCharacters(String s)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      */
readObject(ObjectInputStream ois)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 }
681