001: package com.quadcap.sql.tools;
002:
003: /* Copyright 1999 - 2003 Quadcap Software. All rights reserved.
004: *
005: * This software is distributed under the Quadcap Free Software License.
006: * This software may be used or modified for any purpose, personal or
007: * commercial. Open Source redistributions are permitted. Commercial
008: * redistribution of larger works derived from, or works which bundle
009: * this software requires a "Commercial Redistribution License"; see
010: * http://www.quadcap.com/purchase.
011: *
012: * Redistributions qualify as "Open Source" under one of the following terms:
013: *
014: * Redistributions are made at no charge beyond the reasonable cost of
015: * materials and delivery.
016: *
017: * Redistributions are accompanied by a copy of the Source Code or by an
018: * irrevocable offer to provide a copy of the Source Code for up to three
019: * years at the cost of materials and delivery. Such redistributions
020: * must allow further use, modification, and redistribution of the Source
021: * Code under substantially the same terms as this license.
022: *
023: * Redistributions of source code must retain the copyright notices as they
024: * appear in each source code file, these license terms, and the
025: * disclaimer/limitation of liability set forth as paragraph 6 below.
026: *
027: * Redistributions in binary form must reproduce this Copyright Notice,
028: * these license terms, and the disclaimer/limitation of liability set
029: * forth as paragraph 6 below, in the documentation and/or other materials
030: * provided with the distribution.
031: *
032: * The Software is provided on an "AS IS" basis. No warranty is
033: * provided that the Software is free of defects, or fit for a
034: * particular purpose.
035: *
036: * Limitation of Liability. Quadcap Software shall not be liable
037: * for any damages suffered by the Licensee or any third party resulting
038: * from use of the Software.
039: */
040:
041: import java.io.BufferedInputStream;
042: import java.io.BufferedReader;
043: import java.io.FileInputStream;
044: import java.io.FileReader;
045: import java.io.InputStream;
046: import java.io.InputStreamReader;
047: import java.io.IOException;
048: import java.io.OutputStream;
049: import java.io.PrintWriter;
050: import java.io.Reader;
051:
052: import java.math.BigDecimal;
053:
054: //-//#ifdef JDK11
055: //-import com.quadcap.jdbc.Connection;
056: //-import com.quadcap.jdbc.DatabaseMetaData;
057: //-import com.quadcap.jdbc.ResultSet;
058: //-import com.quadcap.jdbc.ResultSetMetaData;
059: //-import com.quadcap.jdbc.Statement;
060: //-import com.quadcap.sql.types.Type;
061: //#else
062: import java.sql.Connection;
063: import java.sql.DatabaseMetaData;
064: import java.sql.ResultSet;
065: import java.sql.ResultSetMetaData;
066: import java.sql.Statement; //#endif
067:
068: import java.sql.SQLException;
069: import java.sql.Types;
070:
071: import java.util.zip.GZIPInputStream;
072:
073: import org.xml.sax.AttributeList;
074: import org.xml.sax.DocumentHandler;
075: import org.xml.sax.ErrorHandler;
076: import org.xml.sax.InputSource;
077: import org.xml.sax.Parser;
078: import org.xml.sax.Locator;
079: import org.xml.sax.SAXException;
080: import org.xml.sax.SAXParseException;
081:
082: import org.xml.sax.helpers.ParserFactory;
083:
084: /**
085: * This class implements the reverse functionality of XmlDump -- it loads
086: * a database with the data contained in the specified XML file.
087: *
088: * @author Stan Bailes
089: */
090: public class XmlLoad implements DocumentHandler, ErrorHandler {
091: Connection conn;
092: ResultSet rs;
093: ResultSetMetaData rm;
094: Statement stmt;
095: String tableName = "";
096: String columnName = "";
097: StringBuffer data = new StringBuffer();
098:
099: static final int XML = 0;
100: static final int DATABASE = 1;
101: static final int DDL = 2;
102: static final int DML = 3;
103: static final int TABLE = 4;
104: static final int COLUMN = 5;
105:
106: int state = XML;
107:
108: Parser parser;
109: Locator locator;
110:
111: /**
112: * No-argument constructor. The new object needs a database connection
113: * before it can do anything useful.
114: *
115: * @exception Exception may be thrown if there's a problem constructing
116: * the XML parser.
117: */
118: public XmlLoad() throws Exception {
119: parser = ParserFactory
120: .makeParser("com.quadcap.text.sax.Parser");
121: parser.setDocumentHandler(this );
122: parser.setErrorHandler(this );
123: }
124:
125: /**
126: * Construct a new <code>XmlLoad</code> object bound to the specified
127: * connection
128: *
129: * @param conn the database connection
130: *
131: * @exception Exception may be thrown if there's a problem constructing
132: * the XML parser.
133: */
134: public XmlLoad(Connection conn) throws Exception {
135: this ();
136: setConnection(conn);
137: }
138:
139: /**
140: * Set the loader's database connection
141: *
142: * @param conn the new database connection
143: * @exception SQLException may be thrown
144: */
145: public void setConnection(Connection conn) throws SQLException {
146: if (stmt != null) {
147: if (rs != null) {
148: rs.close();
149: }
150: stmt.close();
151: }
152: this .conn = conn;
153: //-//#ifdef JDK11
154: //- this.stmt = (Statement)conn.createStatement();
155: //#else
156: this .stmt = conn.createStatement(
157: ResultSet.TYPE_SCROLL_SENSITIVE,
158: ResultSet.CONCUR_UPDATABLE);
159: //#endif
160: }
161:
162: /**
163: * Get the loader's database connection
164: *
165: * @return the current database connection
166: */
167: public Connection getConnection() {
168: return conn;
169: }
170:
171: /**
172: * Load the XML document from the specified input stream, importing the
173: * data contained therein into the database referenced by the current
174: * connection. The input document should be a well-formed XML
175: * <code><database></code> document.
176: *
177: * @param is the input stream containing the XML document.
178: *
179: * @exception Exception may be thrown if there's a parse error or
180: * a database error.
181: */
182: public void load(InputStream is) throws Exception {
183: parser.parse(new InputSource(is));
184: }
185:
186: /**
187: * Load the XML document from the specified reader, importing the
188: * data contained therein into the database referenced by the current
189: * connection. The input document should be a well-formed XML
190: * <code><database></code> document.
191: *
192: * @param r the input reader containing the XML document.
193: *
194: * @exception Exception may be thrown if there's a parse error or
195: * a database error.
196: */
197: public void load(Reader r) throws Exception {
198: parser.parse(new InputSource(r));
199: }
200:
201: /**
202: * SAX parser callback to handle XML Parser errors.
203: * This implementation just prints them
204: * to System.err.
205: *
206: * @param exception the exception generated by the parser.
207: */
208: public void error(SAXParseException exception) {
209: System.err.println("error");
210: exception.printStackTrace(System.err);
211: }
212:
213: /**
214: * SAX parser callback to handle XML Parser fatal errors.
215: * This implementation just prints them
216: * to System.err.
217: *
218: * @param exception the exception generated by the parser.
219: */
220: public void fatalError(SAXParseException exception) {
221: System.err.println("fatal error");
222: exception.printStackTrace(System.err);
223: }
224:
225: /**
226: * SAX parser callback to handle XML Parser fatal errors.
227: * This implementation just prints them
228: * to System.err.
229: *
230: * @param exception the exception generated by the parser.
231: */
232: public void warning(SAXParseException exception) {
233: System.err.println("warning");
234: exception.printStackTrace(System.err);
235: }
236:
237: /**
238: * SAX parser callback to handle character data found in the
239: * parsed document.
240: *
241: * @param ch the buffer containing the parsed characters.
242: * @param star the buffer position of the first character
243: * @param length the number of characters
244: *
245: * @exception SAXException may be thrown if this data represents
246: * a database value and there's a SQL exception thrown while
247: * trying to update the underlying resultset object with this
248: * data.
249: */
250: public void characters(char[] ch, int start, int length)
251: throws SAXException {
252: data.append(ch, start, length);
253: }
254:
255: /**
256: * SAX parser callback function that is called when the end of the
257: * document is reached. This implementation does nothing.
258: */
259: public void endDocument() {
260: }
261:
262: /**
263: * SAX parser callback function called for the end of an element.
264: * If this element
265: * represents the <code><database></code> element, we finish up
266: * by closing the active statement. If this element represents a table
267: * row element, we insert the current row. Otherwise, we do nothing.
268: *
269: * @param name the name of this element
270: * @exception SAXException may be thrown to wrap any underlying database
271: * exception.
272: */
273: public void endElement(String name) throws SAXException {
274: try {
275: switch (state) {
276: case DATABASE:
277: // all done
278: if (rs != null) {
279: rs.close();
280: rs = null;
281: }
282: if (stmt != null) {
283: stmt.close();
284: stmt = null;
285: }
286: state = XML;
287: break;
288: case TABLE:
289: // done with this row
290: try {
291: rs.insertRow();
292: } catch (Throwable ex) {
293: System.err.println(locator.getSystemId() + ":"
294: + locator.getLineNumber() + ":"
295: + locator.getColumnNumber() + ": "
296: + ex.toString());
297: }
298: state = DML;
299: break;
300: case DDL:
301: try {
302: stmt.execute(data.toString());
303: } catch (Throwable ex) {
304: System.err.println(locator.getSystemId() + ":"
305: + locator.getLineNumber() + ":"
306: + locator.getColumnNumber() + ": "
307: + ex.toString());
308: }
309: state = DATABASE;
310: break;
311: case DML:
312: state = DATABASE;
313: break;
314: case COLUMN:
315: int type = rm.getColumnType(rs.findColumn(columnName));
316: rs.updateObject(columnName, makeObject(type, data
317: .toString()));
318: state = TABLE;
319: break;
320: default:
321: throw makeException("endElement: bad state: " + state);
322: }
323: } catch (Throwable e) {
324: System.err.println(locator.getSystemId() + ":"
325: + locator.getLineNumber() + ":"
326: + locator.getColumnNumber() + ": " + e.toString());
327: System.err.println("[" + data + "]");
328: printException(e);
329: throw makeException(e.toString());
330: }
331: }
332:
333: public SAXException makeException(String s) {
334: return new SAXException(locator.getSystemId() + ":"
335: + locator.getLineNumber() + ":"
336: + locator.getColumnNumber() + ": " + s);
337: }
338:
339: /**
340: * SAX parser callback for ignorable whitespace. We just ignore it
341: *
342: * @param ch the buffer containing the parsed characters.
343: * @param star the buffer position of the first character
344: * @param length the number of characters
345: */
346: public void ignorableWhitespace(char[] ch, int start, int length) {
347: }
348:
349: /**
350: * SAX parser callback for processing instructions. This implementation
351: * does nothing.
352: *
353: * @param target the processing instruction target.
354: * @param data the processing instruction data.
355: */
356: public void processingInstruction(String target, String data) {
357: }
358:
359: /**
360: * SAX parser callback used to receive a document locator.
361: *
362: * @param locator the parser's locator object.
363: */
364: public void setDocumentLocator(Locator locator) {
365: this .locator = locator;
366: }
367:
368: /**
369: * SAX parser callback for document start.
370: * This implementation does nothing.
371: */
372: public void startDocument() {
373: }
374:
375: /**
376: * SAX parser callback for the start of an element. If this element
377: * represents a table row, and the table is different from the last
378: * table seen, we establish an updatable <code>ResultSet</code> for the
379: * new table which can be used to insert new rows into the table.
380: * If this element represents a table row, we move to the insert row.
381: * If this element represents a column, we remember the column name.
382: *
383: * @param name the element name
384: * @param attrs the element's attributes
385: *
386: * @exception SAXException may be thrown to wrap an underlying database
387: * error, or if there is a problem with the XML document itself.
388: */
389: public void startElement(String name, AttributeList attrs)
390: throws SAXException {
391: data.setLength(0);
392: try {
393: switch (state) {
394: case XML:
395: if (!name.equals("database")) {
396: throw makeException("Outer tag must be 'database'");
397: }
398: state = DATABASE;
399: break;
400: case DATABASE:
401: if (name.equals("ddl")) {
402: state = DDL;
403: } else if (name.equals("dml")) {
404: state = DML;
405: } else {
406: throw makeException("Bad tag: " + name
407: + ", expected 'ddl' or 'dml'");
408: }
409: break;
410: case DDL:
411: throw makeException("Nested elements (" + name
412: + ") not allowed in 'ddl' element");
413: case DML:
414: if (!tableName.equals(name)) {
415: if (rs != null)
416: rs.close();
417: rs = (ResultSet) stmt.executeQuery("select * from "
418: + name + " for update");
419: rm = (ResultSetMetaData) rs.getMetaData();
420: }
421: rs.moveToInsertRow();
422: tableName = name;
423: state = TABLE;
424: break;
425: case TABLE:
426: columnName = name;
427: state = COLUMN;
428: break;
429: case COLUMN:
430: throw makeException("Nested elements not allowed in column element");
431: default:
432: throw makeException("bad state: " + state);
433: }
434: } catch (SQLException e) {
435: printException(e);
436: throw makeException(e.toString());
437: }
438: }
439:
440: static byte[] hexMap = new byte[256];
441: static {
442: for (int i = 0; i < XmlDump.hexBytes.length; i++) {
443: hexMap[XmlDump.hexBytes[i]] = (byte) i;
444: }
445: }
446:
447: byte[] makeBytes(String val) {
448: byte[] buf = new byte[val.length() / 2];
449: int pos = 0;
450: for (int i = 0; i < buf.length; i++) {
451: buf[i] = (byte) (hexMap[val.charAt(pos++)] << 4);
452: buf[i] += hexMap[val.charAt(pos++)];
453: }
454: return buf;
455: }
456:
457: Object makeObject(int jdbcType, String val) throws SAXException {
458: switch (jdbcType) {
459: case Types.BIT:
460: return new Boolean(val);
461: case Types.TINYINT:
462: return new Byte(val);
463: case Types.SMALLINT:
464: return new Short(val);
465: case Types.INTEGER:
466: return new Integer(val);
467: case Types.BIGINT:
468: return new Long(val);
469: case Types.FLOAT:
470: case Types.REAL:
471: case Types.DOUBLE:
472: return new Double(val);
473: case Types.NUMERIC:
474: case Types.DECIMAL:
475: return new BigDecimal(val);
476: case Types.CHAR:
477: case Types.VARCHAR:
478: case Types.LONGVARCHAR:
479: case Types.OTHER:
480: //-//#ifdef JDK11
481: //- case Type.CLOB:
482: //#else
483: case Types.CLOB:
484: //#endif
485: return val;
486: case Types.DATE:
487: case Types.TIME:
488: case Types.TIMESTAMP:
489: return val;
490: case Types.BINARY:
491: case Types.VARBINARY:
492: case Types.LONGVARBINARY:
493: //-//#ifdef JDK11
494: //- case Type.BLOB:
495: //#else
496: case Types.BLOB:
497: //#endif
498: return makeBytes(val);
499: case Types.NULL:
500: return null;
501: //#ifndef JDK11
502: case Types.JAVA_OBJECT:
503: case Types.DISTINCT:
504: case Types.STRUCT:
505: case Types.ARRAY:
506: case Types.REF:
507: //#endif
508: default:
509: throw makeException("not implemented, jdbc type: "
510: + jdbcType);
511: }
512: }
513:
514: final static void printException(Throwable t) {
515: if (t != null) {
516: //t.printStackTrace(System.err);
517: com.quadcap.util.Debug.print(t);
518: if (t instanceof SAXException) {
519: printException(((SAXException) t).getException());
520: }
521: }
522: }
523:
524: /**
525: * A main program which supports the command-line invocation of either
526: * the Loader or XmlLoad functions. This function accepts
527: * one or more file names as arguments. </p>
528: *
529: * <p>For each file name that ends with the <code>".sql"</code> extension,
530: * the Loader.loadFile() method is invoked
531: * to execute the SQL commands in the file.</p>
532: *
533: * <p>For each file name that ends with the <code>".xml"</code>
534: * or <code>".xml.gz"</code> extension, the
535: * XmlLoad.load() method is invoked to
536: * load the XML data from that file into the database.
537: *
538: * @param args
539: * <i>database-name</i> <i>file</i> ...
540: */
541: public static void main(String[] args) {
542: try {
543: Connection xconn = XmlDump.makeConnection();
544: try {
545: for (int i = 0; i < args.length; i++) {
546: String f = args[i];
547: if (f.endsWith(".sql")) {
548: Loader load = new Loader(xconn);
549: PrintWriter pw = new PrintWriter(System.out);
550: load.setWriter(pw);
551: load.loadFile(f);
552: pw.flush();
553: } else if (f.endsWith(".xml")) {
554: XmlLoad load = new XmlLoad(xconn);
555: FileReader fr = new FileReader(f);
556: BufferedReader br = new BufferedReader(fr);
557: load.load(br);
558: br.close();
559: } else if (f.endsWith(".xml.gz")) {
560: XmlLoad load = new XmlLoad(xconn);
561: FileInputStream fis = new FileInputStream(f);
562: BufferedInputStream bis = new BufferedInputStream(
563: fis);
564: GZIPInputStream gis = new GZIPInputStream(bis);
565: InputStreamReader ir = new InputStreamReader(
566: gis);
567: load.load(ir);
568: ir.close();
569: }
570: }
571: } finally {
572: xconn.close();
573: }
574: } catch (Exception e) {
575: printException(e);
576: }
577: }
578: }
|