Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that represents an XML document
4
 *  Copyright: 2000 Regents of the University of California and the
5
 *             National Center for Ecological Analysis and Synthesis
6
 *    Authors: Matt Jones
7
 *    Release: @release@
8
 *
9
 *   '$Author: jones $'
10
 *     '$Date: 2000-09-01 17:14:22 -0700 (Fri, 01 Sep 2000) $'
11
 * '$Revision: 429 $'
12
 */
13

    
14
package edu.ucsb.nceas.metacat;
15

    
16
import java.sql.*;
17
import java.io.File;
18
import java.io.FileReader;
19
import java.io.IOException;
20
import java.io.PrintWriter;
21
import java.io.Reader;
22
import java.io.StringWriter;
23
import java.io.Writer;
24

    
25
import java.util.Iterator;
26
import java.util.Stack;
27
import java.util.TreeSet;
28

    
29
import org.xml.sax.AttributeList;
30
import org.xml.sax.ContentHandler;
31
import org.xml.sax.DTDHandler;
32
import org.xml.sax.EntityResolver;
33
import org.xml.sax.ErrorHandler;
34
import org.xml.sax.InputSource;
35
import org.xml.sax.XMLReader;
36
import org.xml.sax.SAXException;
37
import org.xml.sax.SAXParseException;
38
import org.xml.sax.helpers.XMLReaderFactory;
39

    
40
/**
41
 * A class that represents an XML document. It can be created with a simple
42
 * document identifier from a database connection.  It also will write an
43
 * XML text document to a database connection using SAX.
44
 */
45
public class DocumentImpl {
46

    
47
  static final int ALL = 1;
48
  static final int WRITE = 2;
49
  static final int READ = 4;
50

    
51
  private Connection conn = null;
52
  private String docid = null;
53
  private String docname = null;
54
  private String doctype = null;
55
  private String system_id = null;
56
  private long rootnodeid;
57
  private ElementNode rootNode = null;
58
  private String doctitle = null;
59
  private TreeSet nodeRecordList = null;
60

    
61
  /**
62
   * Constructor, creates document from database connection, used 
63
   * for reading the document
64
   *
65
   * @param conn the database connection from which to read the document
66
   * @param docid the identifier of the document to be created
67
   */
68
  public DocumentImpl(Connection conn, String docid) throws McdbException 
69
  {
70
    try {
71
      this.conn = conn;
72
      this.docid = docid;
73
  
74
      // Look up the document information
75
      getDocumentInfo(docid);
76
      
77
      // Download all of the document nodes using a single SQL query
78
      // The sort order of the records is determined by the NodeComparator
79
      // class, and needs to represent a depth-first traversal for the
80
      // toXml() method to work properly
81
      nodeRecordList = getNodeRecordList(rootnodeid);
82
  
83
    } catch (McdbException ex) {
84
      throw ex;
85
    } catch (Throwable t) {
86
      throw new McdbException("Error reading document " + docid + ".");
87
    }
88
  }
89

    
90
  /** 
91
   * Construct a new document instance, writing the contents to the database.
92
   * This method is called from DBSAXHandler because we need to know the
93
   * root element name for documents without a DOCTYPE before creating it.
94
   *
95
   * @param conn the JDBC Connection to which all information is written
96
   * @param rootnodeid - sequence id of the root node in the document
97
   * @param docname - the name of DTD, i.e. the name immediately following 
98
   *        the DOCTYPE keyword ( should be the root element name ) or
99
   *        the root element name if no DOCTYPE declaration provided
100
   *        (Oracle's and IBM parsers are not aware if it is not the 
101
   *        root element name)
102
   * @param doctype - Public ID of the DTD, i.e. the name immediately 
103
   *                  following the PUBLIC keyword in DOCTYPE declaration or
104
   *                  the docname if no Public ID provided or
105
   *                  null if no DOCTYPE declaration provided
106
   *
107
   */
108
  public DocumentImpl(Connection conn, long rootnodeid, String docname, 
109
                      String doctype, String docid, String action, String user)
110
                      throws AccessionNumberException 
111
  {
112
    this.conn = conn;
113
    this.rootnodeid = rootnodeid;
114
    this.docname = docname;
115
    this.doctype = doctype;
116
    this.docid = docid;
117
    writeDocumentToDB(action, user);
118
  }
119

    
120
  /**
121
   * get the document name
122
   */
123
  public String getDocname() {
124
    return docname;
125
  }
126

    
127
  /**
128
   * get the document type (which is the PublicID)
129
   */
130
  public String getDoctype() {
131
    return doctype;
132
  }
133

    
134
  /**
135
   * get the system identifier
136
   */
137
  public String getSystemID() {
138
    return system_id;
139
  }
140

    
141
  /**
142
   * get the root node identifier
143
   */
144
  public long getRootNodeID() {
145
    return rootnodeid;
146
  }
147

    
148
  /** 
149
   * Get the document identifier (docid)
150
   */
151
  public String getDocID() {
152
    return docid;
153
  }
154

    
155

    
156
  /**
157
   * Print a string representation of the XML document
158
   */
159
  public String toString()
160
  {
161
    StringWriter docwriter = new StringWriter();
162
    this.toXml(docwriter);
163
    String document = docwriter.toString();
164
    return document;
165
  }
166

    
167
  /**
168
   * Get a text representation of the XML document as a string
169
   * This older algorithm uses a recursive tree of Objects to represent the
170
   * nodes of the tree.  Each object is passed the data for the document 
171
   * and searches all of the document data to find its children nodes and
172
   * recursively build.  Thus, because each node reads the whole document,
173
   * this algorithm is extremely slow for larger documents, and the time
174
   * to completion is O(N^N) wrt the number of nodes.  See toXml() for a
175
   * better algorithm.
176
   */
177
  public String readUsingSlowAlgorithm()
178
  {
179
    StringBuffer doc = new StringBuffer();
180

    
181
    // Create the elements from the downloaded data in the TreeSet
182
    rootNode = new ElementNode(nodeRecordList, rootnodeid);
183

    
184
    // Append the resulting document to the StringBuffer and return it
185
    doc.append("<?xml version=\"1.0\"?>\n");
186
      
187
    if (docname != null) {
188
      if ((doctype != null) && (system_id != null)) {
189
        doc.append("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
190
                   "\" \"" + system_id + "\">\n");
191
      } else {
192
        doc.append("<!DOCTYPE " + docname + ">\n");
193
      }
194
    }
195
    doc.append(rootNode.toString());
196
  
197
    return (doc.toString());
198
  }
199

    
200
  /**
201
   * Print a text representation of the XML document to a Writer
202
   *
203
   * @param pw the Writer to which we print the document
204
   */
205
  public void toXml(Writer pw)
206
  {
207
    PrintWriter out = null;
208
    if (pw instanceof PrintWriter) {
209
      out = (PrintWriter)pw;
210
    } else {
211
      out = new PrintWriter(pw);
212
    }
213

    
214
    MetaCatUtil util = new MetaCatUtil();
215
    
216
    Stack openElements = new Stack();
217
    boolean atRootElement = true;
218
    boolean previousNodeWasElement = false;
219

    
220
    // Step through all of the node records we were given
221
    Iterator it = nodeRecordList.iterator();
222
    while (it.hasNext()) {
223
      NodeRecord currentNode = (NodeRecord)it.next();
224
      //util.debugMessage("[Got Node ID: " + currentNode.nodeid +
225
                          //" (" + currentNode.parentnodeid +
226
                          //", " + currentNode.nodeindex + 
227
                          //", " + currentNode.nodetype + 
228
                          //", " + currentNode.nodename + 
229
                          //", " + currentNode.nodedata + ")]");
230

    
231
      // Print the end tag for the previous node if needed
232
      //
233
      // This is determined by inspecting the parent nodeid for the
234
      // currentNode.  If it is the same as the nodeid of the last element
235
      // that was pushed onto the stack, then we are still in that previous
236
      // parent element, and we do nothing.  However, if it differs, then we
237
      // have returned to a level above the previous parent, so we go into
238
      // a loop and pop off nodes and print out their end tags until we get
239
      // the node on the stack to match the currentNode parentnodeid
240
      //
241
      // So, this of course means that we rely on the list of elements
242
      // having been sorted in a depth first traversal of the nodes, which
243
      // is handled by the NodeComparator class used by the TreeSet
244
      if (!atRootElement) {
245
        NodeRecord currentElement = (NodeRecord)openElements.peek();
246
        if ( currentNode.parentnodeid != currentElement.nodeid ) {
247
          while ( currentNode.parentnodeid != currentElement.nodeid ) {
248
            currentElement = (NodeRecord)openElements.pop();
249
            util.debugMessage("\n POPPED: " + currentElement.nodename);
250
            out.print("</" + currentElement.nodename + ">" );
251
            currentElement = (NodeRecord)openElements.peek();
252
          }
253
        }
254
      }
255

    
256
      // Handle the DOCUMENT node
257
      if (currentNode.nodetype.equals("DOCUMENT")) {
258
        out.println("<?xml version=\"1.0\"?>");
259
      
260
        if (docname != null) {
261
          if ((doctype != null) && (system_id != null)) {
262
            out.println("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
263
                       "\" \"" + system_id + "\">");
264
          } else {
265
            out.println("<!DOCTYPE " + docname + ">");
266
          }
267
        }
268

    
269
      // Handle the ELEMENT nodes
270
      } else if (currentNode.nodetype.equals("ELEMENT")) {
271
        if (atRootElement) {
272
          atRootElement = false;
273
        } else {
274
          if (previousNodeWasElement) {
275
            out.print(">");
276
          }
277
        }
278
        openElements.push(currentNode);
279
        util.debugMessage("\n PUSHED: " + currentNode.nodename);
280
        previousNodeWasElement = true;
281
        out.print("<" + currentNode.nodename);
282

    
283
      // Handle the ATTRIBUTE nodes
284
      } else if (currentNode.nodetype.equals("ATTRIBUTE")) {
285
        out.print(" " + currentNode.nodename + "=\""
286
                 + currentNode.nodedata + "\"");
287
      } else if (currentNode.nodetype.equals("TEXT")) {
288
        if (previousNodeWasElement) {
289
          out.print(">");
290
        }
291
        out.print(currentNode.nodedata);
292
        previousNodeWasElement = false;
293

    
294
      // Handle the COMMENT nodes
295
      } else if (currentNode.nodetype.equals("COMMENT")) {
296
        if (previousNodeWasElement) {
297
          out.print(">");
298
        }
299
        out.print("<!--" + currentNode.nodedata + "-->");
300
        previousNodeWasElement = false;
301

    
302
      // Handle the PI nodes
303
      } else if (currentNode.nodetype.equals("PI")) {
304
        if (previousNodeWasElement) {
305
          out.print(">");
306
        }
307
        out.print("<?" + currentNode.nodename + " " +
308
                        currentNode.nodedata + "?>");
309
        previousNodeWasElement = false;
310

    
311
      // Handle any other node type (do nothing)
312
      } else {
313
        // Any other types of nodes are not handled.
314
        // Probably should throw an exception here to indicate this
315
      }
316
      out.flush();
317
    }
318

    
319
    // Print the final end tag for the root element
320
    NodeRecord currentElement = (NodeRecord)openElements.pop();
321
    util.debugMessage("\n POPPED: " + currentElement.nodename);
322
    out.print("</" + currentElement.nodename + ">" );
323
    out.flush();
324
  }
325

    
326
  /**
327
   * Look up the document type information from the database
328
   *
329
   * @param docid the id of the document to look up
330
   */
331
  private void getDocumentInfo(String docid) throws McdbException 
332
  {
333
    PreparedStatement pstmt;
334

    
335
    try {
336
      pstmt =
337
        conn.prepareStatement("SELECT docname,doctype,rootnodeid " +
338
                                "FROM xml_documents " +
339
                               "WHERE docid = ?");
340
      // Bind the values to the query
341
      pstmt.setString(1, docid);
342

    
343
      pstmt.execute();
344
      ResultSet rs = pstmt.getResultSet();
345
      boolean tableHasRows = rs.next();
346
      if (tableHasRows) {
347
        this.docname    = rs.getString(1);
348
        this.doctype    = rs.getString(2);
349
        this.rootnodeid = rs.getLong(3);
350
      } 
351
      pstmt.close();
352

    
353
      if (this.doctype != null) {
354
        pstmt =
355
          conn.prepareStatement("SELECT system_id " +
356
                                  "FROM xml_catalog " +
357
                                 "WHERE public_id = ?");
358
        // Bind the values to the query
359
        pstmt.setString(1, doctype);
360
  
361
        pstmt.execute();
362
        rs = pstmt.getResultSet();
363
        tableHasRows = rs.next();
364
        if (tableHasRows) {
365
          this.system_id  = rs.getString(1);
366
        } 
367
        pstmt.close();
368
      }
369
    } catch (SQLException e) {
370
      throw new McdbException("Error accessing database connection.", e);
371
    }
372

    
373
    if (this.docname == null) {
374
      throw new McdbDocNotFoundException("Document not found: " + docid);
375
    }
376
  }
377

    
378
  /**
379
   * Look up the node data from the database
380
   *
381
   * @param rootnodeid the id of the root node of the node tree to look up
382
   */
383
  private TreeSet getNodeRecordList(long rootnodeid) throws McdbException 
384
  {
385
    PreparedStatement pstmt;
386
    TreeSet nodeRecordList = new TreeSet(new NodeComparator());
387
    long nodeid = 0;
388
    long parentnodeid = 0;
389
    long nodeindex = 0;
390
    String nodetype = null;
391
    String nodename = null;
392
    String nodedata = null;
393

    
394
    try {
395
      pstmt =
396
      conn.prepareStatement("SELECT nodeid,parentnodeid,nodeindex, " +
397
           "nodetype,nodename,"+               
398
           "replace(" +
399
           "replace(" +
400
           "replace(nodedata,'&','&amp;') " +
401
           ",'<','&lt;') " +
402
           ",'>','&gt;') " +
403
           "FROM xml_nodes WHERE rootnodeid = ?");
404

    
405
      // Bind the values to the query
406
      pstmt.setLong(1, rootnodeid);
407

    
408
      pstmt.execute();
409
      ResultSet rs = pstmt.getResultSet();
410
      boolean tableHasRows = rs.next();
411
      while (tableHasRows) {
412
        nodeid = rs.getLong(1);
413
        parentnodeid = rs.getLong(2);
414
        nodeindex = rs.getLong(3);
415
        nodetype = rs.getString(4);
416
        nodename = rs.getString(5);
417
        nodedata = rs.getString(6);
418

    
419
        // add the data to the node record list hashtable
420
        NodeRecord currentRecord = new NodeRecord(nodeid, parentnodeid, 
421
                                   nodeindex, nodetype, nodename, nodedata);
422
        nodeRecordList.add(currentRecord);
423

    
424
        // Advance to the next node
425
        tableHasRows = rs.next();
426
      } 
427
      pstmt.close();
428

    
429
    } catch (SQLException e) {
430
      throw new McdbException("Error accessing database connection.", e);
431
    }
432

    
433
    if (nodeRecordList != null) {
434
      return nodeRecordList;
435
    } else {
436
      throw new McdbException("Error getting node data: " + docid);
437
    }
438
  }
439

    
440
  /**
441
   * Write an XML file to the database, given a filename
442
   *
443
   * @param conn the JDBC connection to the database
444
   * @param filename the filename to be loaded into the database
445
   * @param action the action to be performed (INSERT OR UPDATE)
446
   * @param docid the docid to use for the INSERT OR UPDATE
447
   */
448
  public static String write( Connection conn, String filename, String action, 
449
                              String docid, String user, String group )
450
                throws IOException, SQLException, ClassNotFoundException,
451
                       SAXException, SAXParseException, Exception {
452

    
453
    return write(conn, new FileReader(new File(filename).toString()), 
454
                  action, docid, user, group);
455
  }
456
  
457
  /**
458
   * Write an XML file to the database, given a Reader
459
   *
460
   * @param conn the JDBC connection to the database
461
   * @param xml the xml stream to be loaded into the database
462
   * @param action the action to be performed (INSERT OR UPDATE)
463
   * @param docid the docid to use for the INSERT OR UPDATE
464
   */
465
  public static String write( Connection conn, Reader xml, String action, 
466
                              String docid, String user, String group )
467
                throws IOException, SQLException, ClassNotFoundException,
468
                       SAXException, SAXParseException, Exception {
469

    
470
    if ( action.equals("UPDATE") ) {
471
      // Determine if the docid is OK for an UPDATE
472
      AccessionNumber ac = new AccessionNumber();
473
      String newdocid = ac.generate(docid, "UPDATE");
474

    
475
      // b' of the command line invocation
476
      if ( (user != null) &&  (group != null) ) { 
477
        if ( !hasWritePermission(conn, docid, user, group) ) {
478
          throw new Exception("User " + user + 
479
                " does not have permission to update XML Document #" + docid);
480
        }        
481
      }          
482
    }
483

    
484
    try {
485
        XMLReader parser = initializeParser(conn, action, docid, user);
486
        conn.setAutoCommit(false);
487
        parser.parse(new InputSource(xml));
488
        conn.commit();
489
        conn.setAutoCommit(true);
490
        return docid;
491
      } catch (SAXParseException e) {
492
        conn.rollback();
493
        throw e;
494
      } catch (SAXException e) {
495

    
496
        // If its a problem with the accession number its ok, just the 
497
        // accession number was regenerated
498
        AccessionNumberGeneratedException ang = null;
499
        try {
500
          Exception embedded = e.getException();
501
          if ((embedded != null) && 
502
              (embedded instanceof AccessionNumberGeneratedException)) {
503
            ang = (AccessionNumberGeneratedException)e.getException();
504
          }
505
        } catch (ClassCastException cce) {
506
          // Do nothing and just fall through to the ang != null test
507
        }
508
        if (ang != null) {
509
          conn.commit();
510
          conn.setAutoCommit(true);
511
          return (ang.getMessage());
512
        } else {
513
          conn.rollback();
514
          throw e;
515
        }
516
      } catch (Exception e) {
517
        conn.rollback();
518
        throw e;
519
      }
520
  }
521

    
522
  /**
523
   * Delete an XML file from the database (actually, just make it a revision
524
   * in the xml_revisions table)
525
   *
526
   * @param docid the ID of the document to be deleted from the database
527
   */
528
  public static void delete( Connection conn, String docid,
529
                                 String user, String group )
530
                throws IOException, SQLException, ClassNotFoundException, 
531
                       AccessionNumberException, Exception {
532

    
533
    AccessionNumber ac = new AccessionNumber();
534
    String newdocid = ac.generate(docid, "DELETE");
535

    
536
    if ( (user != null) &&  (group != null) ) {
537
      if ( !hasWritePermission(conn, docid, user, group) ) {
538
        throw new Exception("User " + user + 
539
                " does not have permission to delete XML Document #" + docid);
540
      }          
541
    }
542

    
543
    conn.setAutoCommit(false);
544
    // Copy the record to the xml_revisions table
545
    DocumentImpl.archiveDocRevision( conn, docid, user );
546

    
547
    // Now delete it from the xml_documents table
548
    Statement stmt = conn.createStatement();
549
    stmt.execute("DELETE FROM xml_index WHERE docid = '" + docid + "'");
550
    stmt.execute("DELETE FROM xml_documents WHERE docid = '" + docid + "'");
551
    stmt.close();
552
    conn.commit();
553
  }
554
  
555
  /** Check for "write" permissions from DB connection */
556
  private static boolean hasWritePermission(Connection conn, String docid, 
557
                                     String user, String group) 
558
                                     throws SQLException {
559
    PreparedStatement pstmt;
560
    // checking if user is owner of docid
561
    try {
562
      pstmt = conn.prepareStatement(
563
                   "SELECT docid FROM xml_documents " +
564
                   "WHERE docid LIKE ? AND user_owner LIKE ?");
565
      // Bind the values to the query
566
      pstmt.setString(1, docid);
567
      pstmt.setString(2, user);
568

    
569
      pstmt.execute();
570
      ResultSet rs = pstmt.getResultSet();
571
      boolean hasRow = rs.next();
572
      pstmt.close();
573
      if (hasRow) {
574
        return true;
575
      }
576
      
577
    } catch (SQLException e) {
578
      throw new 
579
        SQLException("Error getting document's owner: " + e.getMessage());
580
    }
581

    
582
    // checking access type from xml_access table
583
    int accesstype = 0;
584
    try {
585
      pstmt = conn.prepareStatement(
586
                   "SELECT access_type FROM xml_access " +
587
                   "WHERE docid LIKE ? " + 
588
                   "AND principal_name LIKE ? " +
589
                   "AND principal_type = 'user' " +
590
                   "AND sysdate BETWEEN begin_time AND end_time " +
591
                   "UNION " +
592
                   "SELECT access_type FROM xml_access " +
593
                   "WHERE docid LIKE ? " + 
594
                   "AND principal_name LIKE ? " +
595
                   "AND principal_type = 'group' " +
596
                   "AND sysdate BETWEEN begin_time AND end_time");
597
      // Bind the values to the query
598
      pstmt.setString(1, docid);
599
      pstmt.setString(2, user);
600
      pstmt.setString(3, docid);
601
      pstmt.setString(2, group);
602

    
603
      pstmt.execute();
604
      ResultSet rs = pstmt.getResultSet();
605
      boolean hasRows = rs.next();
606
      while ( hasRows ) {
607
        accesstype = rs.getInt(1);
608
        if ( (accesstype & WRITE) == WRITE ) {
609
          pstmt.close();
610
          return true;
611
        }
612
        hasRows = rs.next();
613
      }
614

    
615
      pstmt.close();
616
      return false;
617
      
618
    } catch (SQLException e) {
619
      throw new 
620
      SQLException("Error getting document's permissions: " + e.getMessage());
621
    }
622
  }
623

    
624
  /** creates SQL code and inserts new document into DB connection */
625
  private void writeDocumentToDB(String action, String user) 
626
               throws AccessionNumberException {
627
    try {
628
      PreparedStatement pstmt = null;
629

    
630
      if (action.equals("INSERT")) {
631
        AccessionNumber ac = new AccessionNumber();
632
        this.docid = ac.generate(docid, "INSERT");
633
        pstmt = conn.prepareStatement(
634
            "INSERT INTO xml_documents " +
635
            "(docid, rootnodeid, docname, doctype, " +
636
            "user_owner, user_updated, date_created, date_updated) " +
637
            "VALUES (?, ?, ?, ?, ?, ?, sysdate, sysdate)");
638
        // Bind the values to the query
639
        pstmt.setString(1, this.docid);
640
        pstmt.setLong(2, rootnodeid);
641
        pstmt.setString(3, docname);
642
        pstmt.setString(4, doctype);
643
        pstmt.setString(5, user);
644
        pstmt.setString(6, user);
645
      } else if (action.equals("UPDATE")) {
646

    
647
        // Save the old document entry in a backup table
648
        DocumentImpl.archiveDocRevision( conn, docid, user );
649

    
650
        // Delete index for the old version of docid
651
        // The new index is inserting on the next calls to DBSAXNode
652
        pstmt = conn.prepareStatement(
653
                "DELETE FROM xml_index WHERE docid='" + this.docid + "'");
654
        pstmt.execute();
655
        pstmt.close();
656

    
657
        // Update the new document to reflect the new node tree
658
        pstmt = conn.prepareStatement(
659
            "UPDATE xml_documents " +
660
            "SET rootnodeid = ?, docname = ?, doctype = ?, " +
661
            "user_updated = ?, date_updated = sysdate WHERE docid LIKE ?");
662
        // Bind the values to the query
663
        pstmt.setLong(1, rootnodeid);
664
        pstmt.setString(2, docname);
665
        pstmt.setString(3, doctype);
666
        pstmt.setString(4, user);
667
        pstmt.setString(5, this.docid);
668
      } else {
669
        System.err.println("Action not supported: " + action);
670
      }
671

    
672
      // Do the insertion
673
      pstmt.execute();
674
      pstmt.close();
675

    
676
    } catch (SQLException e) {
677
      System.out.println(e.getMessage());
678
    } catch (AccessionNumberException ane) {
679
      MetaCatUtil.debugMessage("Invalid accession number.");
680
      MetaCatUtil.debugMessage(ane.getMessage());
681
      throw ane;
682
    } catch (Exception e) {
683
      System.out.println(e.getMessage());
684
    }
685
  }
686

    
687
  /**
688
   * Get the document title
689
   */
690
  public String getTitle() {
691
    return doctitle;
692
  }
693

    
694
  /**
695
   * Set the document title
696
   *
697
   * @param title the new title for the document
698
   */
699
  public void setTitle( String title ) {
700
    this.doctitle = title;
701
    try {
702
      PreparedStatement pstmt;
703
      pstmt = conn.prepareStatement(
704
            "UPDATE xml_documents " +
705
            " SET doctitle = ? " +
706
            "WHERE docid = ?");
707

    
708
      // Bind the values to the query
709
      pstmt.setString(1, doctitle);
710
      pstmt.setString(2, docid);
711

    
712
      // Do the insertion
713
      pstmt.execute();
714
      pstmt.close();
715
    } catch (SQLException e) {
716
      System.out.println(e.getMessage());
717
    }
718
  }
719

    
720
  /**
721
   * Look up the title of the first child element named "title"
722
   * and record it as the document title
723
   */
724
  public void setTitleFromChildElement() {
725
    String title = null;
726
    long assigned_id=0;
727
    PreparedStatement pstmt;
728
    try {
729
      pstmt = conn.prepareStatement(
730
              "SELECT nodedata FROM xml_nodes " +
731
              "WHERE nodetype = 'TEXT' " +
732
              "AND rootnodeid = ? " +
733
              "AND parentnodeid IN " +
734
              "  (SELECT nodeid FROM xml_nodes " +
735
              "  WHERE nodename = 'title' " +
736
              "  AND nodetype =  'ELEMENT' " +
737
              "  AND rootnodeid = ? ) " +
738
              "ORDER BY nodeid");
739

    
740
      // The above query might be slow, and probably will be because
741
      // it gets ALL of the title elements while searching for one
742
      // title in a small subtree but it avoids the problem of using
743
      // Oracle's Hierarchical Query syntax which is not portable --
744
      // the commented out SQL that follows shows an equivalent query
745
      // using Oracle-specific hierarchical query
746
      /*
747
      pstmt = conn.prepareStatement(
748
              "SELECT nodedata FROM xml_nodes " +
749
              "WHERE nodetype = 'TEXT' " +
750
              "AND parentnodeid IN " +
751
              "(SELECT nodeid FROM xml_nodes " +
752
              "WHERE nodename = 'title' " +
753
              "START WITH nodeid = ? " +
754
              "CONNECT BY PRIOR nodeid = parentnodeid)");
755
      */
756

    
757
      // Bind the values to the query
758
      pstmt.setLong(1, rootnodeid);
759
      pstmt.setLong(2, rootnodeid);
760

    
761
      pstmt.execute();
762
      ResultSet rs = pstmt.getResultSet();
763
      boolean tableHasRows = rs.next();
764
      if (tableHasRows) {
765
        title = rs.getString(1);
766
      }
767
      pstmt.close();
768
    } catch (SQLException e) {
769
      System.out.println("Error getting title: " + e.getMessage());
770
    }
771

    
772
    // assign the new title
773
    this.setTitle(title);
774
  }
775

    
776
  /** Save a document entry in the xml_revisions table */
777
  private static void archiveDocRevision(Connection conn, String docid,
778
                                         String user) throws SQLException {
779
    // create a record in xml_revisions table 
780
    // for that document as selected from xml_documents
781
    PreparedStatement pstmt = conn.prepareStatement(
782
      "INSERT INTO xml_revisions " +
783
        "(revisionid, docid, rootnodeid, docname, doctype, doctitle, " +
784
        "user_owner, user_updated, date_created, date_updated) " +
785
      "SELECT null, ?, rootnodeid, docname, doctype, doctitle," + 
786
        "user_owner, ?, sysdate, sysdate "+
787
      "FROM xml_documents " +
788
      "WHERE docid = ?");
789
    // Bind the values to the query and execute it
790
    pstmt.setString(1, docid);
791
    pstmt.setString(2, user);
792
    pstmt.setString(3, docid);
793
    pstmt.execute();
794
    pstmt.close();
795

    
796
  }
797

    
798
  /** Save a document entry in the xml_revisions table */
799
/*
800
  private static void archiveDocRevision(Connection conn, String docid,
801
                                         String user) throws SQLException {
802
    // First get all of the values we need
803
    long rnodeid = -1;
804
    String docname = null;
805
    String doctype = null;
806
    String doctitle = null;
807
    String user_owner = null;
808
    Date date_created = null;
809
    PreparedStatement pstmt = conn.prepareStatement(
810
      "SELECT rootnodeid,docname,doctype,doctitle,user_owner,date_created " +
811
      "FROM xml_documents " +
812
      "WHERE docid = ?");
813
    // Bind the values to the query and execute it
814
    pstmt.setString(1, docid);
815
    pstmt.execute();
816

    
817
    ResultSet rs = pstmt.getResultSet();
818
    boolean tableHasRows = rs.next();
819
    if (tableHasRows) {
820
      rnodeid      = rs.getLong(1);
821
      docname      = rs.getString(2);
822
      doctype      = rs.getString(3);
823
      doctitle     = rs.getString(4);
824
      user_owner   = rs.getString(5);
825
      date_created = rs.getDate(6);
826
    }
827
    pstmt.close();
828

    
829
    MetaCatUtil.debugMessage(new Long(rnodeid).toString());
830
    MetaCatUtil.debugMessage(docname);
831
    MetaCatUtil.debugMessage(doctitle);
832
    //MetaCatUtil.debugMessage(date_created.toString());
833

    
834
    // Next create the new record in the other table using the values selected
835
    pstmt = conn.prepareStatement(
836
       "INSERT INTO xml_revisions " +
837
       "(revisionid, docid, rootnodeid, docname, doctype, doctitle, " +
838
       "user_owner, user_updated, date_created, date_updated) " +
839
       "VALUES (null, ?, ?, ?, ?, ?, ?, ?, sysdate, sysdate)");
840
    // Bind the values to the query and execute it
841
    pstmt.setString(1, docid);
842
    pstmt.setLong(2, rnodeid);
843
    pstmt.setString(3, docname);
844
    pstmt.setString(4, doctype);
845
    pstmt.setString(5, doctitle);
846
    pstmt.setString(6, user_owner);
847
    pstmt.setString(7, user);
848
    //pstmt.setDate(6, date_created);
849
    pstmt.execute();
850
    pstmt.close();
851
  }
852
*/
853
  /**
854
   * Set up the parser handlers for writing the document to the database
855
   */
856
  private static XMLReader initializeParser(Connection conn,
857
                           String action, String docid, String user) {
858
    XMLReader parser = null;
859
    //
860
    // Set up the SAX document handlers for parsing
861
    //
862
    try {
863
      ContentHandler chandler   = new DBSAXHandler(conn, action, docid, user);
864
      EntityResolver dbresolver = new DBEntityResolver(conn, 
865
                                      (DBSAXHandler)chandler);
866
      DTDHandler dtdhandler     = new DBDTDHandler(conn);
867

    
868
      // Get an instance of the parser
869
      MetaCatUtil util = new MetaCatUtil();
870
      String parserName = util.getOption("saxparser");
871
      parser = XMLReaderFactory.createXMLReader(parserName);
872

    
873
      // Turn off validation
874
      parser.setFeature("http://xml.org/sax/features/validation", false);
875
      
876
      // Set Handlers in the parser
877
      parser.setProperty("http://xml.org/sax/properties/declaration-handler",
878
                         chandler);
879
      parser.setProperty("http://xml.org/sax/properties/lexical-handler",
880
                         chandler);
881
      parser.setContentHandler((ContentHandler)chandler);
882
      parser.setEntityResolver((EntityResolver)dbresolver);
883
      parser.setDTDHandler((DTDHandler)dtdhandler);
884
      parser.setErrorHandler((ErrorHandler)chandler);
885

    
886
    } catch (Exception e) {
887
       System.err.println(e.toString());
888
    }
889

    
890
    return parser;
891
  }
892

    
893
  /**
894
   * the main routine used to test the DBWriter utility.
895
   * <p>
896
   * Usage: java DocumentImpl <-f filename -a action -d docid>
897
   *
898
   * @param filename the filename to be loaded into the database
899
   * @param action the action to perform (READ, INSERT, UPDATE, DELETE)
900
   * @param docid the id of the document to process
901
   */
902
  static public void main(String[] args) {
903
     
904
    try {
905
      String filename = null;
906
      String action   = null;
907
      String docid    = null;
908
      boolean showRuntime = false;
909
      boolean useOldReadAlgorithm = false;
910

    
911
      // Parse the command line arguments
912
      for ( int i=0 ; i < args.length; ++i ) {
913
        if ( args[i].equals( "-f" ) ) {
914
          filename =  args[++i];
915
        } else if ( args[i].equals( "-a" ) ) {
916
          action =  args[++i];
917
        } else if ( args[i].equals( "-d" ) ) {
918
          docid =  args[++i];
919
        } else if ( args[i].equals( "-t" ) ) {
920
          showRuntime = true;
921
        } else if ( args[i].equals( "-old" ) ) {
922
          useOldReadAlgorithm = true;
923
        } else {
924
          System.err.println
925
            ( "   args[" +i+ "] '" +args[i]+ "' ignored." );
926
        }
927
      }
928
      
929
      // Check if the required arguments are provided
930
      boolean argsAreValid = false;
931
      if (action != null) {
932
        if (action.equals("INSERT")) {
933
          if (filename != null) {
934
            argsAreValid = true;
935
          } 
936
        } else if (action.equals("UPDATE")) {
937
          if ((filename != null) && (docid != null)) {
938
            argsAreValid = true;
939
          } 
940
        } else if (action.equals("DELETE")) {
941
          if (docid != null) {
942
            argsAreValid = true;
943
          } 
944
        } else if (action.equals("READ")) {
945
          if (docid != null) {
946
            argsAreValid = true;
947
          } 
948
        } 
949
      } 
950

    
951
      // Print usage message if the arguments are not valid
952
      if (!argsAreValid) {
953
        System.err.println("Wrong number of arguments!!!");
954
        System.err.println(
955
          "USAGE: java DocumentImpl [-t] <-a INSERT> [-d docid] <-f filename>");
956
        System.err.println(
957
          "   OR: java DocumentImpl [-t] <-a UPDATE -d docid -f filename>");
958
        System.err.println(
959
          "   OR: java DocumentImpl [-t] <-a DELETE -d docid>");
960
        System.err.println(
961
          "   OR: java DocumentImpl [-t] [-old] <-a READ -d docid>");
962
        return;
963
      }
964
      
965
      // Time the request if asked for
966
      double startTime = System.currentTimeMillis();
967
      
968
      // Open a connection to the database
969
      MetaCatUtil util = new MetaCatUtil();
970
      Connection dbconn = util.openDBConnection();
971

    
972
      // Execute the action requested (READ, INSERT, UPDATE, DELETE)
973
      if (action.equals("READ")) {
974
          DocumentImpl xmldoc = new DocumentImpl( dbconn, docid );
975
          if (useOldReadAlgorithm) {
976
            System.out.println(xmldoc.readUsingSlowAlgorithm());
977
          } else {
978
            xmldoc.toXml(new PrintWriter(System.out));
979
          }
980
      } else if (action.equals("DELETE")) {
981
        DocumentImpl.delete(dbconn, docid, null, null);
982
        System.out.println("Document deleted: " + docid);
983
      } else {
984
        String newdocid = DocumentImpl.write(dbconn, filename, action, docid,
985
                                                                  null, null);
986
        if ((docid != null) && (!docid.equals(newdocid))) {
987
          if (action.equals("INSERT")) {
988
            System.out.println("New document ID generated!!! ");
989
          } else if (action.equals("UPDATE")) {
990
            System.out.println("ERROR: Couldn't update document!!! ");
991
          }
992
        } else if ((docid == null) && (action.equals("UPDATE"))) {
993
          System.out.println("ERROR: Couldn't update document!!! ");
994
        }
995
        System.out.println("Document processing finished for: " + filename
996
              + " (" + newdocid + ")");
997
      }
998

    
999
      double stopTime = System.currentTimeMillis();
1000
      double executionTime = (stopTime - startTime)/1000;
1001
      if (showRuntime) {
1002
        System.out.println("\n\nExecution time was: " + 
1003
                           executionTime + " seconds");
1004
      }
1005
    } catch (McdbException me) {
1006
      me.toXml(new PrintWriter(System.err));
1007
    } catch (AccessionNumberException ane) {
1008
      System.out.println("ERROR: Couldn't delete document!!! ");
1009
      System.out.println(ane.getMessage());
1010
    } catch (Exception e) {
1011
      System.err.println("EXCEPTION HANDLING REQUIRED");
1012
      System.err.println(e.getMessage());
1013
      e.printStackTrace(System.err);
1014
    }
1015
  }
1016
}
(15-15/27)