Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that represents an XML node and its contents
4
 *  Copyright: 2000 Regents of the University of California and the
5
 *             National Center for Ecological Analysis and Synthesis
6
 *    Authors: Matt Jones
7
 *
8
 *   '$Author: leinfelder $'
9
 *     '$Date: 2011-11-04 14:45:59 -0700 (Fri, 04 Nov 2011) $'
10
 * '$Revision: 6606 $'
11
 *
12
 * This program is free software; you can redistribute it and/or modify
13
 * it under the terms of the GNU General Public License as published by
14
 * the Free Software Foundation; either version 2 of the License, or
15
 * (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU General Public License
23
 * along with this program; if not, write to the Free Software
24
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25
 */
26

    
27
package edu.ucsb.nceas.metacat;
28

    
29
import java.sql.PreparedStatement;
30
import java.sql.ResultSet;
31
import java.sql.SQLException;
32
import java.util.Enumeration;
33
import java.util.Hashtable;
34

    
35
import org.apache.log4j.Logger;
36
import org.xml.sax.SAXException;
37

    
38
import edu.ucsb.nceas.metacat.database.DBConnection;
39
import edu.ucsb.nceas.metacat.database.DBConnectionPool;
40
import edu.ucsb.nceas.metacat.database.DatabaseService;
41

    
42
/**
43
 * A Class that represents an XML node and its contents and
44
 * can write its own representation to a database connection
45
 */
46
public class DBSAXNode extends BasicNode {
47

    
48
  private DBConnection	connection;
49
  private DBSAXNode	parentNode;
50
  private Logger logMetacat = Logger.getLogger(DBSAXNode.class);
51

    
52
  /**
53
   * Construct a new node instance for DOCUMENT nodes
54
   *
55
   * @param conn the JDBC Connection to which all information is written
56
   */
57
  public DBSAXNode (DBConnection conn, String docid) throws SAXException {
58

    
59
    super();
60
    this.connection = conn;
61
    this.parentNode = null;
62
    writeChildNodeToDB("DOCUMENT", null, null, docid);
63
    updateRootNodeID(getNodeID());
64
  }
65

    
66
  /**
67
   * Construct a new node instance for ELEMENT nodes
68
   *
69
   * @param conn the JDBC Connection to which all information is written
70
   * @param tagname the name of the node
71
   * @param parentNode the parent node for this node being created
72
   */
73
  public DBSAXNode (DBConnection conn, String qName, String lName,
74
                    DBSAXNode parentNode, long rootnodeid,
75
                    String docid, String doctype)
76
                                               throws SAXException {
77

    
78
    super(lName);
79
    this.connection = conn;
80
    this.parentNode = parentNode;
81
    setParentID(parentNode.getNodeID());
82
    setRootNodeID(rootnodeid);
83
    setDocID(docid);
84
    setNodeIndex(parentNode.incChildNum());
85
    writeChildNodeToDB("ELEMENT", qName, null, docid);
86
    //No writing XML Index from here. New Thread used instead.
87
    //updateNodeIndex(docid, doctype);
88
  }
89

    
90
  /**
91
   * Construct a new node instance for DTD nodes
92
   * This Node will write docname, publicId and systemId into db. Only
93
   * handle systemid  existed.(external dtd)
94
   *
95
   * @param conn the JDBC Connection to which all information is written
96
   * @param tagname the name of the node
97
   * @param parentNode the parent node for this node being created
98
   */
99
  public DBSAXNode (DBConnection conn, String docName, String publicId,
100
                    String systemId, DBSAXNode parentNode, long rootnodeid,
101
                    String docid) throws SAXException
102
  {
103

    
104
    super();
105
    this.connection = conn;
106
    this.parentNode = parentNode;
107
    setParentID(parentNode.getNodeID());
108
    setRootNodeID(rootnodeid);
109
    setDocID(docid);
110
    // insert dtd node only for external dtd definiation
111
    if (systemId != null)
112
    {
113
      //write docname to DB
114
      if (docName != null)
115
      {
116
        setNodeIndex(parentNode.incChildNum());
117
        writeDTDNodeToDB(DocumentImpl.DOCNAME, docName, docid);
118
      }
119
      //write publicId to DB
120
      if (publicId != null)
121
      {
122
        setNodeIndex(parentNode.incChildNum());
123
        writeDTDNodeToDB(DocumentImpl.PUBLICID, publicId, docid);
124
      }
125
      //write systemId to DB
126
      setNodeIndex(parentNode.incChildNum());
127
      writeDTDNodeToDB(DocumentImpl.SYSTEMID, systemId, docid);
128
    }
129
  }
130
  
131
  /** creates SQL code and inserts new node into DB connection */
132
  public long writeChildNodeToDB(String nodetype, String nodename,
133
                                 String data, String docid)
134
                                 throws SAXException {
135
    String limitedData = null;
136
    int leftover = 0;
137
    if (data != null) {
138
    	leftover = data.length();
139
    }
140
    int offset = 0;
141
    boolean moredata = true;
142
    long endNodeId = -1;
143

    
144
    // This loop deals with the case where there are more characters
145
    // than can fit in a single database text field (limit is
146
    // MAXDATACHARS). If the text to be inserted exceeds MAXDATACHARS,
147
    // write a series of nodes that are MAXDATACHARS long, and then the
148
    // final node contains the remainder
149
    while (moredata) {
150
        if (leftover > (DBSAXHandler.MAXDATACHARS)) {
151
        	limitedData = data.substring(offset, DBSAXHandler.MAXDATACHARS);
152
            leftover -= (DBSAXHandler.MAXDATACHARS - 1);
153
            offset += (DBSAXHandler.MAXDATACHARS - 1);
154
        } else {
155
        	if (data != null) {
156
        		limitedData = data.substring(offset, offset + leftover);
157
        	} else {
158
        		limitedData = null;
159
        	}
160
        	moredata = false;
161
        }
162
        
163
        endNodeId =  writeChildNodeToDBDataLimited(nodetype, nodename, limitedData, docid);
164
    }
165
    
166
    return endNodeId;
167
  }
168

    
169
  /** creates SQL code and inserts new node into DB connection */
170
  public long writeChildNodeToDBDataLimited(String nodetype, String nodename,
171
                                 String data, String docid)
172
                                 throws SAXException
173
  {
174
    long nid = -1;
175
    try
176
    {
177

    
178
      PreparedStatement pstmt;
179
      
180
      if (nodetype == "DOCUMENT") {
181
        pstmt = connection.prepareStatement(
182
            "INSERT INTO xml_nodes " +
183
            "(nodetype, nodename, nodeprefix, docid) " +
184
            "VALUES (?, ?, ?, ?)");
185

    
186
        logMetacat.debug("DBSAXNode.writeChildNodeToDBDataLimited - inserting doc name: " + nodename);
187
      } else {
188
          pstmt = connection.prepareStatement(
189
              "INSERT INTO xml_nodes " +
190
              "(nodetype, nodename, nodeprefix, docid, " +
191
              "rootnodeid, parentnodeid, nodedata, nodeindex) " +
192
              "VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
193
      }
194
      
195
      // Increase DBConnection usage count
196
      connection.increaseUsageCount(1);
197
      
198
      // Bind the values to the query
199
      pstmt.setString(1, nodetype);
200
      int idx;
201
      if ( nodename != null && (idx = nodename.indexOf(":")) != -1 ) {
202
        pstmt.setString(2, nodename.substring(idx+1));
203
        pstmt.setString(3, nodename.substring(0,idx));
204
      } else {
205
        pstmt.setString(2, nodename);
206
        pstmt.setString(3, null);
207
      }
208
      pstmt.setString(4, docid);
209
      if (nodetype != "DOCUMENT") {
210
        if (nodetype == "ELEMENT") {
211
          pstmt.setLong(5, getRootNodeID());
212
          pstmt.setLong(6, getParentID());
213
          pstmt.setString(7, data);
214
          pstmt.setInt(8, getNodeIndex());
215
        } else {
216
          pstmt.setLong(5, getRootNodeID());
217
          pstmt.setLong(6, getNodeID());
218
          pstmt.setString(7, data);
219
          pstmt.setInt(8, incChildNum());
220
        }
221
      }
222
      // Do the insertion
223
      logMetacat.debug("DBSAXNode.writeChildNodeToDBDataLimited - SQL insert: " + pstmt.toString());
224
      pstmt.execute();
225
      pstmt.close();
226

    
227
      // get the generated unique id afterward
228
      nid = DatabaseService.getInstance().getDBAdapter().getUniqueID(connection.getConnections(), "xml_nodes");
229
      //should increase connection usage!!!!!!
230

    
231
      if (nodetype.equals("DOCUMENT")) {
232
        // Record the root node id that was generated from the database
233
        setRootNodeID(nid);
234
      }
235

    
236
      if (nodetype.equals("DOCUMENT") || nodetype.equals("ELEMENT")) {
237

    
238
        // Record the node id that was generated from the database
239
        setNodeID(nid);
240

    
241
        // Record the node type that was passed to the method
242
        setNodeType(nodetype);
243

    
244
      }
245

    
246
    } catch (SQLException sqle) {
247
      logMetacat.error("DBSAXNode.writeChildNodeToDBDataLimited - SQL error inserting node: " + 
248
    		  "(" + nodetype + ", " + nodename + ", " + data + ") : " + sqle.getMessage());
249
      sqle.printStackTrace(System.err);
250
      throw new SAXException(sqle.getMessage());
251
    }
252
    return nid;
253
  }
254

    
255
  /**
256
   * update rootnodeid=nodeid for 'DOCUMENT' type of nodes only
257
   */
258
  public void updateRootNodeID(long nodeid) throws SAXException {
259
      try {
260
        PreparedStatement pstmt;
261
        pstmt = connection.prepareStatement(
262
              "UPDATE xml_nodes set rootnodeid = ? " +
263
              "WHERE nodeid = ?");
264
        // Increase DBConnection usage count
265
        connection.increaseUsageCount(1);
266

    
267
        // Bind the values to the query
268
        pstmt.setLong(1, nodeid);
269
        pstmt.setLong(2, nodeid);
270
        // Do the update
271
        pstmt.execute();
272
        pstmt.close();
273
      } catch (SQLException e) {
274
        System.out.println("Error in DBSaxNode.updateRootNodeID: " +
275
                           e.getMessage());
276
        throw new SAXException(e.getMessage());
277
      }
278
  }
279

    
280
  /**
281
   * creates SQL code to put nodename for the document node
282
   * into DB connection
283
   */
284
  public void writeNodename(String nodename) throws SAXException {
285
      try {
286
        PreparedStatement pstmt;
287
        pstmt = connection.prepareStatement(
288
              "UPDATE xml_nodes set nodename = ? " +
289
              "WHERE nodeid = ?");
290
        // Increase DBConnection usage count
291
        connection.increaseUsageCount(1);
292

    
293
        // Bind the values to the query
294
        pstmt.setString(1, nodename);
295
        pstmt.setLong(2, getNodeID());
296
        // Do the insertion
297
        pstmt.execute();
298
        pstmt.close();
299
      } catch (SQLException e) {
300
        System.out.println("Error in DBSaxNode.writeNodeName: " +
301
                           e.getMessage());
302
        throw new SAXException(e.getMessage());
303
      }
304
  }
305

    
306
 /** creates SQL code and inserts new node into DB connection */
307
  public long writeDTDNodeToDB(String nodename, String data, String docid)
308
                                 throws SAXException
309
  {
310
    long nid = -1;
311
    try
312
    {
313

    
314
      PreparedStatement pstmt;
315
      logMetacat.info("DBSAXNode.writeDTDNodeToDB - Insert dtd into db: "+nodename +" "+data);
316
  
317
      pstmt = connection.prepareStatement(
318
              "INSERT INTO xml_nodes " +
319
              "(nodetype, nodename, docid, " +
320
              "rootnodeid, parentnodeid, nodedata, nodeindex) " +
321
              "VALUES (?, ?, ?, ?, ?, ?, ?)");
322

    
323
       // Increase DBConnection usage count
324
      connection.increaseUsageCount(1);
325

    
326
      // Bind the values to the query
327
      pstmt.setString(1, DocumentImpl.DTD);
328
      pstmt.setString(2, nodename);
329
      pstmt.setString(3, docid);
330
      pstmt.setLong(4, getRootNodeID());
331
      pstmt.setLong(5, getParentID());
332
      pstmt.setString(6, data);
333
      pstmt.setInt(7, getNodeIndex());
334

    
335
      // Do the insertion
336
      pstmt.execute();
337
      pstmt.close();
338

    
339
      // get the generated unique id afterward
340
      nid = DatabaseService.getInstance().getDBAdapter().getUniqueID(connection.getConnections(), "xml_nodes");
341

    
342
    } catch (SQLException e) {
343
      System.out.println("Error in DBSaxNode.writeDTDNodeToDB");
344
      System.err.println("Error inserting node: (" + DocumentImpl.DTD + ", " +
345
                                                     nodename + ", " +
346
                                                     data + ")" );
347
      System.err.println(e.getMessage());
348
      e.printStackTrace(System.err);
349
      throw new SAXException(e.getMessage());
350
    }
351
    return nid;
352
  }
353

    
354

    
355
  /** get next node id from DB connection */
356
  private long generateNodeID() throws SAXException {
357
      long nid=0;
358
      PreparedStatement pstmt;
359
      DBConnection dbConn = null;
360
      int serialNumber = -1;
361
      try {
362
        // Get DBConnection
363
        dbConn=DBConnectionPool.getDBConnection("DBSAXNode.generateNodeID");
364
        serialNumber=dbConn.getCheckOutSerialNumber();
365
        String sql = "SELECT xml_nodes_id_seq.nextval FROM dual";
366
        pstmt = dbConn.prepareStatement(sql);
367
        pstmt.execute();
368
        ResultSet rs = pstmt.getResultSet();
369
        boolean tableHasRows = rs.next();
370
        if (tableHasRows) {
371
          nid = rs.getLong(1);
372
        }
373
        pstmt.close();
374
      } catch (SQLException e) {
375
        System.out.println("Error in DBSaxNode.generateNodeID: " +
376
                            e.getMessage());
377
        throw new SAXException(e.getMessage());
378
      }
379
      finally
380
      {
381
        // Return DBconnection
382
        DBConnectionPool.returnDBConnection(dbConn, serialNumber);
383
      }//finally
384

    
385
      return nid;
386
  }
387

    
388
  /** Add a new attribute to this node, or set its value */
389
  public long setAttribute(String attName, String attValue, String docid)
390
              throws SAXException
391
  {
392
    long nodeId = -1;
393
    if (attName != null)
394
    {
395
      // Enter the attribute in the hash table
396
      super.setAttribute(attName, attValue);
397

    
398
      // And enter the attribute in the database
399
      nodeId = writeChildNodeToDB("ATTRIBUTE", attName, attValue, docid);
400
    }
401
    else
402
    {
403
      System.err.println("Attribute name must not be null!");
404
      throw new SAXException("Attribute name must not be null!");
405
    }
406
    return nodeId;
407
  }
408

    
409
  /** Add a namespace to this node */
410
  public long setNamespace(String prefix, String uri, String docid)
411
              throws SAXException
412
  {
413
    long nodeId = -1;
414
    if (prefix != null)
415
    {
416
      // Enter the namespace in a hash table
417
      super.setNamespace(prefix, uri);
418
      // And enter the namespace in the database
419
      nodeId = writeChildNodeToDB("NAMESPACE", prefix, uri, docid);
420
    }
421
    else
422
    {
423
      System.err.println("Namespace prefix must not be null!");
424
      throw new SAXException("Namespace prefix must not be null!");
425
    }
426
    return nodeId;
427
  }
428

    
429

    
430

    
431
  /**
432
   * USED FROM SEPARATE THREAD RUNNED from DBSAXHandler on endDocument()
433
   * Update the node index (xml_index) for this node by generating
434
   * test strings that represent all of the relative and absolute
435
   * paths through the XML tree from document root to this node
436
   */
437
  public void updateNodeIndex(DBConnection conn, String docid, String doctype)
438
               throws SAXException
439
  {
440
    Hashtable pathlist = new Hashtable();
441
    boolean atStartingNode = true;
442
    boolean atRootDocumentNode = false;
443
    DBSAXNode nodePointer = this;
444
    StringBuffer currentPath = new StringBuffer();
445
    int counter = 0;
446

    
447
    // Create a Hashtable of all of the paths to reach this node
448
    // including absolute paths and relative paths
449
    while (!atRootDocumentNode) {
450
      if (atStartingNode) {
451
        currentPath.insert(0, nodePointer.getTagName());
452
        pathlist.put(currentPath.toString(), new Long(getNodeID()));
453
        counter++;
454
        atStartingNode = false;
455
      } else {
456
        currentPath.insert(0, "/");
457
        currentPath.insert(0, nodePointer.getTagName());
458
        pathlist.put(currentPath.toString(), new Long(getNodeID()));
459
        counter++;
460
      }
461

    
462
      // advance to the next parent node
463
      nodePointer = nodePointer.getParentNode();
464

    
465
      // If we're at the DOCUMENT node (root of DOM tree), add
466
      // the root "/" to make the absolute path
467
      if (nodePointer.getNodeType().equals("DOCUMENT")) {
468
        currentPath.insert(0, "/");
469
        pathlist.put(currentPath.toString(), new Long(getNodeID()));
470
        counter++;
471
        atRootDocumentNode = true;
472
      }
473
    }
474

    
475
    try {
476
      // Create an insert statement to reuse for all of the path insertions
477
      PreparedStatement pstmt = conn.prepareStatement(
478
              "INSERT INTO xml_index (nodeid, path, docid, doctype, " +
479
               "parentnodeid) " +
480
              "VALUES (?, ?, ?, ?, ?)");
481
      // Increase usage count
482
      conn.increaseUsageCount(1);
483

    
484
      pstmt.setString(3, docid);
485
      pstmt.setString(4, doctype);
486
      pstmt.setLong(5, getParentID());
487

    
488
      // Step through the hashtable and insert each of the path values
489
      Enumeration en = pathlist.keys();
490
      while (en.hasMoreElements()) {
491
        String path = (String)en.nextElement();
492
        Long nodeid = (Long)pathlist.get(path);
493
        pstmt.setLong(1, nodeid.longValue());
494
        pstmt.setString(2, path);
495

    
496
        pstmt.executeUpdate();
497
      }
498
      // Close the database statement
499
      pstmt.close();
500
    } catch (SQLException sqe) {
501
      System.err.println("SQL Exception while inserting path to index in " +
502
                         "DBSAXNode.updateNodeIndex for document " + docid);
503
      System.err.println(sqe.getMessage());
504
      throw new SAXException(sqe.getMessage());
505
    }
506
  }
507

    
508
  /** get the parent of this node */
509
  public DBSAXNode getParentNode() {
510
    return parentNode;
511
  }
512
}
(19-19/63)