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
26package com.sun.rowset.internal;
27
28import com.sun.rowset.JdbcRowSetResourceBundle;
29import java.sql.*;
30import javax.sql.*;
31import java.io.*;
32import java.text.MessageFormat;
33import java.util.*;
34
35import javax.sql.rowset.*;
36import 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
43public 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("&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}
681