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: berkley $'
10
 *     '$Date: 2000-09-26 15:06:52 -0700 (Tue, 26 Sep 2000) $'
11
 * '$Revision: 465 $'
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 doctitle = null;
56
  private String createdate = null;
57
  private String updatedate = null;
58
  private String system_id = null;
59
  private long rootnodeid;
60
  private ElementNode rootNode = null;
61
  private TreeSet nodeRecordList = null;
62

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

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

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

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

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

    
143
  /**
144
   * get the root node identifier
145
   */
146
  public long getRootNodeID() {
147
    return rootnodeid;
148
  }
149
  
150
  /**
151
   * get the creation date
152
   */
153
  public String getCreateDate() {
154
    return createdate;
155
  }
156
  
157
  /**
158
   * get the update date
159
   */
160
  public String getUpdateDate() {
161
    return updatedate;
162
  }
163

    
164
  /** 
165
   * Get the document identifier (docid)
166
   */
167
  public String getDocID() {
168
    return docid;
169
  }
170
  
171
  /**
172
   *get the document title
173
   */
174
  public String getDocTitle() {
175
    return doctitle;
176
  }
177

    
178

    
179
  /**
180
   * Print a string representation of the XML document
181
   */
182
  public String toString()
183
  {
184
    StringWriter docwriter = new StringWriter();
185
    this.toXml(docwriter);
186
    String document = docwriter.toString();
187
    return document;
188
  }
189

    
190
  /**
191
   * Get a text representation of the XML document as a string
192
   * This older algorithm uses a recursive tree of Objects to represent the
193
   * nodes of the tree.  Each object is passed the data for the document 
194
   * and searches all of the document data to find its children nodes and
195
   * recursively build.  Thus, because each node reads the whole document,
196
   * this algorithm is extremely slow for larger documents, and the time
197
   * to completion is O(N^N) wrt the number of nodes.  See toXml() for a
198
   * better algorithm.
199
   */
200
  public String readUsingSlowAlgorithm()
201
  {
202
    StringBuffer doc = new StringBuffer();
203

    
204
    // Create the elements from the downloaded data in the TreeSet
205
    rootNode = new ElementNode(nodeRecordList, rootnodeid);
206

    
207
    // Append the resulting document to the StringBuffer and return it
208
    doc.append("<?xml version=\"1.0\"?>\n");
209
      
210
    if (docname != null) {
211
      if ((doctype != null) && (system_id != null)) {
212
        doc.append("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
213
                   "\" \"" + system_id + "\">\n");
214
      } else {
215
        doc.append("<!DOCTYPE " + docname + ">\n");
216
      }
217
    }
218
    doc.append(rootNode.toString());
219
  
220
    return (doc.toString());
221
  }
222

    
223
  /**
224
   * Print a text representation of the XML document to a Writer
225
   *
226
   * @param pw the Writer to which we print the document
227
   */
228
  public void toXml(Writer pw)
229
  {
230
    PrintWriter out = null;
231
    if (pw instanceof PrintWriter) {
232
      out = (PrintWriter)pw;
233
    } else {
234
      out = new PrintWriter(pw);
235
    }
236

    
237
    MetaCatUtil util = new MetaCatUtil();
238
    
239
    Stack openElements = new Stack();
240
    boolean atRootElement = true;
241
    boolean previousNodeWasElement = false;
242

    
243
    // Step through all of the node records we were given
244
    Iterator it = nodeRecordList.iterator();
245
    while (it.hasNext()) {
246
      NodeRecord currentNode = (NodeRecord)it.next();
247
      //util.debugMessage("[Got Node ID: " + currentNode.nodeid +
248
                          //" (" + currentNode.parentnodeid +
249
                          //", " + currentNode.nodeindex + 
250
                          //", " + currentNode.nodetype + 
251
                          //", " + currentNode.nodename + 
252
                          //", " + currentNode.nodedata + ")]");
253

    
254
      // Print the end tag for the previous node if needed
255
      //
256
      // This is determined by inspecting the parent nodeid for the
257
      // currentNode.  If it is the same as the nodeid of the last element
258
      // that was pushed onto the stack, then we are still in that previous
259
      // parent element, and we do nothing.  However, if it differs, then we
260
      // have returned to a level above the previous parent, so we go into
261
      // a loop and pop off nodes and print out their end tags until we get
262
      // the node on the stack to match the currentNode parentnodeid
263
      //
264
      // So, this of course means that we rely on the list of elements
265
      // having been sorted in a depth first traversal of the nodes, which
266
      // is handled by the NodeComparator class used by the TreeSet
267
      if (!atRootElement) {
268
        NodeRecord currentElement = (NodeRecord)openElements.peek();
269
        if ( currentNode.parentnodeid != currentElement.nodeid ) {
270
          while ( currentNode.parentnodeid != currentElement.nodeid ) {
271
            currentElement = (NodeRecord)openElements.pop();
272
            util.debugMessage("\n POPPED: " + currentElement.nodename);
273
            if (previousNodeWasElement) {
274
              out.print(">");
275
              previousNodeWasElement = false;
276
            }  
277
            out.print("</" + currentElement.nodename + ">" );
278
            currentElement = (NodeRecord)openElements.peek();
279
          }
280
        }
281
      }
282

    
283
      // Handle the DOCUMENT node
284
      if (currentNode.nodetype.equals("DOCUMENT")) {
285
        out.println("<?xml version=\"1.0\"?>");
286
      
287
        if (docname != null) {
288
          if ((doctype != null) && (system_id != null)) {
289
            out.println("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
290
                       "\" \"" + system_id + "\">");
291
          } else {
292
            out.println("<!DOCTYPE " + docname + ">");
293
          }
294
        }
295

    
296
      // Handle the ELEMENT nodes
297
      } else if (currentNode.nodetype.equals("ELEMENT")) {
298
        if (atRootElement) {
299
          atRootElement = false;
300
        } else {
301
          if (previousNodeWasElement) {
302
            out.print(">");
303
          }
304
        }
305
        openElements.push(currentNode);
306
        util.debugMessage("\n PUSHED: " + currentNode.nodename);
307
        previousNodeWasElement = true;
308
        out.print("<" + currentNode.nodename);
309

    
310
      // Handle the ATTRIBUTE nodes
311
      } else if (currentNode.nodetype.equals("ATTRIBUTE")) {
312
        out.print(" " + currentNode.nodename + "=\""
313
                 + currentNode.nodedata + "\"");
314
      } else if (currentNode.nodetype.equals("TEXT")) {
315
        if (previousNodeWasElement) {
316
          out.print(">");
317
        }
318
        out.print(currentNode.nodedata);
319
        previousNodeWasElement = false;
320

    
321
      // Handle the COMMENT nodes
322
      } else if (currentNode.nodetype.equals("COMMENT")) {
323
        if (previousNodeWasElement) {
324
          out.print(">");
325
        }
326
        out.print("<!--" + currentNode.nodedata + "-->");
327
        previousNodeWasElement = false;
328

    
329
      // Handle the PI nodes
330
      } else if (currentNode.nodetype.equals("PI")) {
331
        if (previousNodeWasElement) {
332
          out.print(">");
333
        }
334
        out.print("<?" + currentNode.nodename + " " +
335
                        currentNode.nodedata + "?>");
336
        previousNodeWasElement = false;
337

    
338
      // Handle any other node type (do nothing)
339
      } else {
340
        // Any other types of nodes are not handled.
341
        // Probably should throw an exception here to indicate this
342
      }
343
      out.flush();
344
    }
345

    
346
    // Print the final end tag for the root element
347
    NodeRecord currentElement = (NodeRecord)openElements.pop();
348
    util.debugMessage("\n POPPED: " + currentElement.nodename);
349
    out.print("</" + currentElement.nodename + ">" );
350
    out.flush();
351
  }
352

    
353
  /**
354
   * Look up the document type information from the database
355
   *
356
   * @param docid the id of the document to look up
357
   */
358
  private void getDocumentInfo(String docid) throws McdbException 
359
  {
360
    PreparedStatement pstmt;
361

    
362
    try {
363
      pstmt =
364
        conn.prepareStatement("SELECT docname, doctype, rootnodeid,doctitle, " +
365
                              "date_created, date_updated " + 
366
                               "FROM xml_documents " +
367
                               "WHERE docid LIKE ?");
368
      // Bind the values to the query
369
      pstmt.setString(1, docid);
370

    
371
      pstmt.execute();
372
      ResultSet rs = pstmt.getResultSet();
373
      boolean tableHasRows = rs.next();
374
      if (tableHasRows) {
375
        this.docname    = rs.getString(1);
376
        this.doctype    = rs.getString(2);
377
        this.rootnodeid = rs.getLong(3);
378
        this.doctitle   = rs.getString(4);
379
        this.createdate = rs.getString(5);
380
        this.updatedate = rs.getString(6);
381
      } 
382
      pstmt.close();
383

    
384
      if (this.doctype != null) {
385
        pstmt =
386
          conn.prepareStatement("SELECT system_id " +
387
                                  "FROM xml_catalog " +
388
                                 "WHERE public_id LIKE ?");
389
        // Bind the values to the query
390
        pstmt.setString(1, doctype);
391
  
392
        pstmt.execute();
393
        rs = pstmt.getResultSet();
394
        tableHasRows = rs.next();
395
        if (tableHasRows) {
396
          this.system_id  = rs.getString(1);
397
        } 
398
        pstmt.close();
399
      }
400
    } catch (SQLException e) {
401
      throw new McdbException("Error accessing database connection.", e);
402
    }
403

    
404
    if (this.docname == null) {
405
      throw new McdbDocNotFoundException("Document not found: " + docid);
406
    }
407
  }
408

    
409
  /**
410
   * Look up the node data from the database
411
   *
412
   * @param rootnodeid the id of the root node of the node tree to look up
413
   */
414
  private TreeSet getNodeRecordList(long rootnodeid) throws McdbException 
415
  {
416
    PreparedStatement pstmt;
417
    TreeSet nodeRecordList = new TreeSet(new NodeComparator());
418
    long nodeid = 0;
419
    long parentnodeid = 0;
420
    long nodeindex = 0;
421
    String nodetype = null;
422
    String nodename = null;
423
    String nodedata = null;
424

    
425
    try {
426
      pstmt =
427
      conn.prepareStatement("SELECT nodeid,parentnodeid,nodeindex, " +
428
           "nodetype,nodename,"+               
429
           "replace(" +
430
           "replace(" +
431
           "replace(nodedata,'&','&amp;') " +
432
           ",'<','&lt;') " +
433
           ",'>','&gt;') " +
434
           "FROM xml_nodes WHERE rootnodeid = ?");
435

    
436
      // Bind the values to the query
437
      pstmt.setLong(1, rootnodeid);
438

    
439
      pstmt.execute();
440
      ResultSet rs = pstmt.getResultSet();
441
      boolean tableHasRows = rs.next();
442
      while (tableHasRows) {
443
        nodeid = rs.getLong(1);
444
        parentnodeid = rs.getLong(2);
445
        nodeindex = rs.getLong(3);
446
        nodetype = rs.getString(4);
447
        nodename = rs.getString(5);
448
        nodedata = rs.getString(6);
449

    
450
        // add the data to the node record list hashtable
451
        NodeRecord currentRecord = new NodeRecord(nodeid, parentnodeid, 
452
                                   nodeindex, nodetype, nodename, nodedata);
453
        nodeRecordList.add(currentRecord);
454

    
455
        // Advance to the next node
456
        tableHasRows = rs.next();
457
      } 
458
      pstmt.close();
459

    
460
    } catch (SQLException e) {
461
      throw new McdbException("Error accessing database connection.", e);
462
    }
463

    
464
    if (nodeRecordList != null) {
465
      return nodeRecordList;
466
    } else {
467
      throw new McdbException("Error getting node data: " + docid);
468
    }
469
  }
470

    
471
 /** creates SQL code and inserts new document into DB connection */
472
  private void writeDocumentToDB(String action, String user) 
473
               throws SQLException, Exception {
474
    try {
475
      PreparedStatement pstmt = null;
476

    
477
      if (action.equals("INSERT")) {
478
        //AccessionNumber ac = new AccessionNumber();
479
        //this.docid = ac.generate(docid, "INSERT");
480
        pstmt = conn.prepareStatement(
481
            "INSERT INTO xml_documents " +
482
            "(docid, rootnodeid, docname, doctype, " +
483
            "user_owner, user_updated, date_created, date_updated) " +
484
            "VALUES (?, ?, ?, ?, ?, ?, sysdate, sysdate)");
485
        // Bind the values to the query
486
        pstmt.setString(1, this.docid);
487
        pstmt.setLong(2, rootnodeid);
488
        pstmt.setString(3, docname);
489
        pstmt.setString(4, doctype);
490
        pstmt.setString(5, user);
491
        pstmt.setString(6, user);
492
      } else if (action.equals("UPDATE")) {
493

    
494
        // Save the old document entry in a backup table
495
        DocumentImpl.archiveDocRevision( conn, docid, user );
496

    
497
        // Delete index for the old version of docid
498
        // The new index is inserting on the next calls to DBSAXNode
499
        pstmt = conn.prepareStatement(
500
                "DELETE FROM xml_index WHERE docid='" + this.docid + "'");
501
        pstmt.execute();
502
        pstmt.close();
503

    
504
        // Update the new document to reflect the new node tree
505
        pstmt = conn.prepareStatement(
506
            "UPDATE xml_documents " +
507
            "SET rootnodeid = ?, docname = ?, doctype = ?, " +
508
            "user_updated = ?, date_updated = sysdate WHERE docid LIKE ?");
509
        // Bind the values to the query
510
        pstmt.setLong(1, rootnodeid);
511
        pstmt.setString(2, docname);
512
        pstmt.setString(3, doctype);
513
        pstmt.setString(4, user);
514
        pstmt.setString(5, this.docid);
515
      } else {
516
        System.err.println("Action not supported: " + action);
517
      }
518

    
519
      // Do the insertion
520
      pstmt.execute();
521
      pstmt.close();
522

    
523
    } catch (SQLException sqle) {
524
      throw sqle;
525
//    } catch (AccessionNumberException ane) {
526
//      MetaCatUtil.debugMessage("Invalid accession number.");
527
//      MetaCatUtil.debugMessage(ane.getMessage());
528
//      throw ane;
529
    } catch (Exception e) {
530
      throw e;
531
    }
532
  }
533

    
534
  /**
535
   * Get the document title
536
   */
537
  public String getTitle() {
538
    return doctitle;
539
  }
540

    
541
  /**
542
   * Set the document title
543
   *
544
   * @param title the new title for the document
545
   */
546

    
547
  public void setTitle( String title ) {
548
    this.doctitle = title;
549
    try {
550
      PreparedStatement pstmt;
551
      pstmt = conn.prepareStatement(
552
            "UPDATE xml_documents " +
553
            " SET doctitle = ? " +
554
            "WHERE docid = ?");
555

    
556
      // Bind the values to the query
557
      pstmt.setString(1, doctitle);
558
      pstmt.setString(2, docid);
559

    
560
      // Do the insertion
561
      pstmt.execute();
562
      pstmt.close();
563
    } catch (SQLException e) {
564
      System.out.println(e.getMessage());
565
    }
566
  }
567

    
568
  /**
569
   * Look up the title of the first child element named "title"
570
   * and record it as the document title
571
   */
572
/*   NOT NEEDED ANY MORE
573
  public void setTitleFromChildElement() {
574
    String title = null;
575
    long assigned_id=0;
576
    PreparedStatement pstmt;
577
    try {
578
      pstmt = conn.prepareStatement(
579
              "SELECT nodedata FROM xml_nodes " +
580
              "WHERE nodetype = 'TEXT' " +
581
              "AND rootnodeid = ? " +
582
              "AND parentnodeid IN " +
583
              "  (SELECT nodeid FROM xml_nodes " +
584
              "  WHERE nodename = 'title' " +
585
              "  AND nodetype =  'ELEMENT' " +
586
              "  AND rootnodeid = ? ) " +
587
              "ORDER BY nodeid");
588
*/
589
      // The above query might be slow, and probably will be because
590
      // it gets ALL of the title elements while searching for one
591
      // title in a small subtree but it avoids the problem of using
592
      // Oracle's Hierarchical Query syntax which is not portable --
593
      // the commented out SQL that follows shows an equivalent query
594
      // using Oracle-specific hierarchical query
595
      /*
596
      pstmt = conn.prepareStatement(
597
              "SELECT nodedata FROM xml_nodes " +
598
              "WHERE nodetype = 'TEXT' " +
599
              "AND parentnodeid IN " +
600
              "(SELECT nodeid FROM xml_nodes " +
601
              "WHERE nodename = 'title' " +
602
              "START WITH nodeid = ? " +
603
              "CONNECT BY PRIOR nodeid = parentnodeid)");
604
      */
605
/*
606
      // Bind the values to the query
607
      pstmt.setLong(1, rootnodeid);
608
      pstmt.setLong(2, rootnodeid);
609

    
610
      pstmt.execute();
611
      ResultSet rs = pstmt.getResultSet();
612
      boolean tableHasRows = rs.next();
613
      if (tableHasRows) {
614
        title = rs.getString(1);
615
      }
616
      pstmt.close();
617
    } catch (SQLException e) {
618
      System.out.println("Error getting title: " + e.getMessage());
619
    }
620

    
621
    // assign the new title
622
    this.setTitle(title);
623
  }
624
*/
625

    
626
  /**
627
   * Write an XML file to the database, given a filename
628
   *
629
   * @param conn the JDBC connection to the database
630
   * @param filename the filename to be loaded into the database
631
   * @param action the action to be performed (INSERT OR UPDATE)
632
   * @param docid the docid to use for the INSERT OR UPDATE
633
   */
634
  public static String write( Connection conn, String filename, String action, 
635
                              String docid, String user, String group )
636
                throws Exception {
637

    
638
    return write(conn, new FileReader(new File(filename).toString()), 
639
                  action, docid, user, group);
640
  }
641
  
642
  /**
643
   * Write an XML file to the database, given a Reader
644
   *
645
   * @param conn the JDBC connection to the database
646
   * @param xml the xml stream to be loaded into the database
647
   * @param action the action to be performed (INSERT OR UPDATE)
648
   * @param docid the docid to use for the INSERT OR UPDATE
649
   */
650
  public static String write( Connection conn, Reader xml, String action, 
651
                              String docid, String user, String group )
652
                throws Exception {
653

    
654
    // Determine if the docid is OK for INSERT or UPDATE
655
    AccessionNumber ac = new AccessionNumber(conn);
656
    String newdocid = ac.generate(docid, action);
657

    
658
    if ( action.equals("UPDATE") ) {
659
      // check for 'write' permission for 'user' to update this document
660
      if ( !hasWritePermission(conn, docid, user, group) ) {
661
        throw new Exception("User " + user + 
662
              " does not have permission to update XML Document #" + docid);
663
      }          
664
    }
665

    
666
    try {
667
        XMLReader parser = initializeParser(conn, action, newdocid, user);
668
        conn.setAutoCommit(false);
669
        parser.parse(new InputSource(xml));
670
        conn.commit();
671
        conn.setAutoCommit(true);
672
        //return newdocid;
673
      } catch (SAXParseException e) {
674
        conn.rollback();
675
        conn.setAutoCommit(true);
676
        throw e;
677
      } catch (SAXException e) {
678
        conn.rollback();
679
        conn.setAutoCommit(true);
680
        throw e;
681
/*
682
        // If its a problem with the accession number its ok, just the 
683
        // accession number was regenerated
684
        AccessionNumberGeneratedException ang = null;
685
        try {
686
          Exception embedded = e.getException();
687
          if ((embedded != null) && 
688
              (embedded instanceof AccessionNumberGeneratedException)) {
689
            ang = (AccessionNumberGeneratedException)e.getException();
690
          }
691
        } catch (ClassCastException cce) {
692
          // Do nothing and just fall through to the ang != null test
693
        }
694
        if (ang != null) {
695
          conn.commit();
696
          conn.setAutoCommit(true);
697
          return (ang.getMessage());
698
        } else {
699
          conn.rollback();
700
          throw e;
701
        }
702
*/        
703
      } catch (Exception e) {
704
        conn.rollback();
705
        conn.setAutoCommit(true);
706
        throw e;
707
      }
708
      
709
      if ( (docid != null) && !(newdocid.equals(docid)) ) {
710
        return new String("New document ID generated:" + newdocid);
711
      } else {
712
        return newdocid;
713
      }
714
  }
715

    
716
  /**
717
   * Delete an XML file from the database (actually, just make it a revision
718
   * in the xml_revisions table)
719
   *
720
   * @param docid the ID of the document to be deleted from the database
721
   */
722
  public static void delete( Connection conn, String docid,
723
                                 String user, String group )
724
                throws Exception {
725

    
726
    // Determine if the docid is OK for DELETE
727
    AccessionNumber ac = new AccessionNumber(conn);
728
    String newdocid = ac.generate(docid, "DELETE");
729

    
730
    // check for 'write' permission for 'user' to delete this document
731
    if ( !hasWritePermission(conn, docid, user, group) ) {
732
      throw new Exception("User " + user + 
733
              " does not have permission to delete XML Document #" + docid);
734
    }
735

    
736
    conn.setAutoCommit(false);
737
    // Copy the record to the xml_revisions table
738
    DocumentImpl.archiveDocRevision( conn, docid, user );
739

    
740
    // Now delete it from the xml_documents table
741
    Statement stmt = conn.createStatement();
742
    stmt.execute("DELETE FROM xml_index WHERE docid = '" + docid + "'");
743
    stmt.execute("DELETE FROM xml_documents WHERE docid = '" + docid + "'");
744
    stmt.close();
745
    conn.commit();
746
    conn.setAutoCommit(true);
747
  }
748
  
749
  /** Check for "write" permissions from DB connection */
750
  private static boolean hasWritePermission(Connection conn, String docid, 
751
                                     String user, String group) 
752
                                     throws SQLException {
753
    // b' of the command line invocation
754
    if ( (user == null) && (group == null) ) {
755
      return true;
756
    }
757

    
758
    PreparedStatement pstmt;
759
    // checking if user is owner of docid
760
    try {
761
      pstmt = conn.prepareStatement(
762
                   "SELECT 'x' FROM xml_documents " +
763
                   "WHERE docid LIKE ? AND user_owner LIKE ?");
764
      // Bind the values to the query
765
      pstmt.setString(1, docid);
766
      pstmt.setString(2, user);
767

    
768
      pstmt.execute();
769
      ResultSet rs = pstmt.getResultSet();
770
      boolean hasRow = rs.next();
771
      pstmt.close();
772
      if (hasRow) {
773
        return true;
774
      }
775
      
776
    } catch (SQLException e) {
777
      throw new 
778
        SQLException("Error checking document's owner: " + e.getMessage());
779
    }
780

    
781
    // checking access type from xml_access table
782
    int accesstype = 0;
783
    try {
784
      pstmt = conn.prepareStatement(
785
                   "SELECT access_type FROM xml_access " +
786
                   "WHERE docid LIKE ? " + 
787
                   "AND principal_name LIKE ? " +
788
                   "AND principal_type = 'user' " +
789
                   "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
790
                                   "AND nvl(end_time,sysdate) " +
791
                   "UNION " +
792
                   "SELECT access_type FROM xml_access " +
793
                   "WHERE docid LIKE ? " + 
794
                   "AND principal_name LIKE ? " +
795
                   "AND principal_type = 'group' " +
796
                   "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
797
                                   "AND nvl(end_time,sysdate)");
798
      // Bind the values to the query
799
      pstmt.setString(1, docid);
800
      pstmt.setString(2, user);
801
      pstmt.setString(3, docid);
802
      pstmt.setString(2, group);
803

    
804
      pstmt.execute();
805
      ResultSet rs = pstmt.getResultSet();
806
      boolean hasRows = rs.next();
807
      while ( hasRows ) {
808
        accesstype = rs.getInt(1);
809
        if ( (accesstype & WRITE) == WRITE ) {
810
          pstmt.close();
811
          return true;
812
        }
813
        hasRows = rs.next();
814
      }
815

    
816
      pstmt.close();
817
      return false;
818
      
819
    } catch (SQLException e) {
820
      throw new 
821
      SQLException("Error getting document's permissions: " + e.getMessage());
822
    }
823
  }
824

    
825
  /**
826
   * Set up the parser handlers for writing the document to the database
827
   */
828
  private static XMLReader initializeParser(Connection conn,
829
                           String action, String docid, String user) 
830
                           throws Exception {
831
    XMLReader parser = null;
832
    //
833
    // Set up the SAX document handlers for parsing
834
    //
835
    try {
836
      ContentHandler chandler   = new DBSAXHandler(conn, action, docid, user);
837
      EntityResolver dbresolver = new DBEntityResolver(conn, 
838
                                      (DBSAXHandler)chandler);
839
      DTDHandler dtdhandler     = new DBDTDHandler(conn);
840

    
841
      // Get an instance of the parser
842
      MetaCatUtil util = new MetaCatUtil();
843
      String parserName = util.getOption("saxparser");
844
      parser = XMLReaderFactory.createXMLReader(parserName);
845

    
846
      // Turn off validation
847
      parser.setFeature("http://xml.org/sax/features/validation", false);
848
      
849
      // Set Handlers in the parser
850
      parser.setProperty("http://xml.org/sax/properties/declaration-handler",
851
                         chandler);
852
      parser.setProperty("http://xml.org/sax/properties/lexical-handler",
853
                         chandler);
854
      parser.setContentHandler((ContentHandler)chandler);
855
      parser.setEntityResolver((EntityResolver)dbresolver);
856
      parser.setDTDHandler((DTDHandler)dtdhandler);
857
      parser.setErrorHandler((ErrorHandler)chandler);
858

    
859
    } catch (Exception e) {
860
      throw e;
861
    }
862

    
863
    return parser;
864
  }
865

    
866
  /** Save a document entry in the xml_revisions table */
867
  private static void archiveDocRevision(Connection conn, String docid,
868
                                         String user) throws SQLException {
869
    // create a record in xml_revisions table 
870
    // for that document as selected from xml_documents
871
    PreparedStatement pstmt = conn.prepareStatement(
872
      "INSERT INTO xml_revisions " +
873
        "(revisionid, docid, rootnodeid, docname, doctype, doctitle, " +
874
        "user_owner, user_updated, date_created, date_updated) " +
875
      "SELECT null, ?, rootnodeid, docname, doctype, doctitle," + 
876
        "user_owner, ?, sysdate, sysdate "+
877
      "FROM xml_documents " +
878
      "WHERE docid = ?");
879
    // Bind the values to the query and execute it
880
    pstmt.setString(1, docid);
881
    pstmt.setString(2, user);
882
    pstmt.setString(3, docid);
883
    pstmt.execute();
884
    pstmt.close();
885

    
886
  }
887

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

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

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

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

    
995
      double stopTime = System.currentTimeMillis();
996
      double dbOpenTime = (connTime - startTime)/1000;
997
      double insertTime = (stopTime - connTime)/1000;
998
      double executionTime = (stopTime - startTime)/1000;
999
      if (showRuntime) {
1000
        System.out.println("\n\nTotal Execution time was: " + 
1001
                           executionTime + " seconds.");
1002
        System.out.println("Time to open DB connection was: " + dbOpenTime + 
1003
                           " seconds.");
1004
        System.out.println("Time to insert document was: " + insertTime +
1005
                           " seconds.");
1006
      }
1007
      dbconn.close();
1008
    } catch (McdbException me) {
1009
      me.toXml(new PrintWriter(System.err));
1010
    } catch (AccessionNumberException ane) {
1011
      System.out.println("ERROR: Couldn't delete document!!! ");
1012
      System.out.println(ane.getMessage());
1013
    } catch (Exception e) {
1014
      System.err.println("EXCEPTION HANDLING REQUIRED");
1015
      System.err.println(e.getMessage());
1016
      e.printStackTrace(System.err);
1017
    }
1018
  }
1019
}
(15-15/28)