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-11-22 14:55:19 -0800 (Wed, 22 Nov 2000) $'
11
 * '$Revision: 561 $'
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
import java.io.InputStreamReader;
25

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

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

    
41
import java.net.URL;
42

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

    
50
  static final int ALL = 1;
51
  static final int WRITE = 2;
52
  static final int READ = 4;
53

    
54
  private Connection conn = null;
55
  private String docid = null;
56
  private String docname = null;
57
  private String doctype = null;
58
  private String doctitle = null;
59
  private String createdate = null;
60
  private String updatedate = null;
61
  private String system_id = null;
62
  private String userowner = null;
63
  private String userupdated = null;
64
  private int serverlocation;
65
  private int publicaccess; 
66
  private long rootnodeid;
67
  private ElementNode rootNode = null;
68
  private TreeSet nodeRecordList = null;
69

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

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

    
142
  /**
143
   * get the document name
144
   */
145
  public String getDocname() {
146
    return docname;
147
  }
148

    
149
  /**
150
   * get the document type (which is the PublicID)
151
   */
152
  public String getDoctype() {
153
    return doctype;
154
  }
155

    
156
  /**
157
   * get the system identifier
158
   */
159
  public String getSystemID() {
160
    return system_id;
161
  }
162

    
163
  /**
164
   * get the root node identifier
165
   */
166
  public long getRootNodeID() {
167
    return rootnodeid;
168
  }
169
  
170
  /**
171
   * get the creation date
172
   */
173
  public String getCreateDate() {
174
    return createdate;
175
  }
176
  
177
  /**
178
   * get the update date
179
   */
180
  public String getUpdateDate() {
181
    return updatedate;
182
  }
183

    
184
  /** 
185
   * Get the document identifier (docid)
186
   */
187
  public String getDocID() {
188
    return docid;
189
  }
190
  
191
  /**
192
   *get the document title
193
   */
194
  public String getDocTitle() {
195
    return doctitle;
196
  }
197
  
198
  public String getUserowner() {
199
    return userowner;
200
  }
201
  
202
  public String getUserupdated() {
203
    return userupdated;
204
  }
205
  
206
  public int getServerlocation() {
207
    return serverlocation;
208
  }
209
  
210
  public int getPublicaccess() {
211
    return publicaccess;
212
  }
213

    
214
  /**
215
   * Print a string representation of the XML document
216
   */
217
  public String toString()
218
  {
219
    StringWriter docwriter = new StringWriter();
220
    this.toXml(docwriter);
221
    String document = docwriter.toString();
222
    return document;
223
  }
224

    
225
  /**
226
   * Get a text representation of the XML document as a string
227
   * This older algorithm uses a recursive tree of Objects to represent the
228
   * nodes of the tree.  Each object is passed the data for the document 
229
   * and searches all of the document data to find its children nodes and
230
   * recursively build.  Thus, because each node reads the whole document,
231
   * this algorithm is extremely slow for larger documents, and the time
232
   * to completion is O(N^N) wrt the number of nodes.  See toXml() for a
233
   * better algorithm.
234
   */
235
  public String readUsingSlowAlgorithm()
236
  {
237
    StringBuffer doc = new StringBuffer();
238

    
239
    // Create the elements from the downloaded data in the TreeSet
240
    rootNode = new ElementNode(nodeRecordList, rootnodeid);
241

    
242
    // Append the resulting document to the StringBuffer and return it
243
    doc.append("<?xml version=\"1.0\"?>\n");
244
      
245
    if (docname != null) {
246
      if ((doctype != null) && (system_id != null)) {
247
        doc.append("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
248
                   "\" \"" + system_id + "\">\n");
249
      } else {
250
        doc.append("<!DOCTYPE " + docname + ">\n");
251
      }
252
    }
253
    doc.append(rootNode.toString());
254
  
255
    return (doc.toString());
256
  }
257

    
258
  /**
259
   * Print a text representation of the XML document to a Writer
260
   *
261
   * @param pw the Writer to which we print the document
262
   */
263
  public void toXml(Writer pw)
264
  {
265
    PrintWriter out = null;
266
    if (pw instanceof PrintWriter) {
267
      out = (PrintWriter)pw;
268
    } else {
269
      out = new PrintWriter(pw);
270
    }
271

    
272
    MetaCatUtil util = new MetaCatUtil();
273
    
274
    Stack openElements = new Stack();
275
    boolean atRootElement = true;
276
    boolean previousNodeWasElement = false;
277

    
278
    // Step through all of the node records we were given
279
    Iterator it = nodeRecordList.iterator();
280
    while (it.hasNext()) {
281
      NodeRecord currentNode = (NodeRecord)it.next();
282
      //util.debugMessage("[Got Node ID: " + currentNode.nodeid +
283
                          //" (" + currentNode.parentnodeid +
284
                          //", " + currentNode.nodeindex + 
285
                          //", " + currentNode.nodetype + 
286
                          //", " + currentNode.nodename + 
287
                          //", " + currentNode.nodedata + ")]");
288

    
289
      // Print the end tag for the previous node if needed
290
      //
291
      // This is determined by inspecting the parent nodeid for the
292
      // currentNode.  If it is the same as the nodeid of the last element
293
      // that was pushed onto the stack, then we are still in that previous
294
      // parent element, and we do nothing.  However, if it differs, then we
295
      // have returned to a level above the previous parent, so we go into
296
      // a loop and pop off nodes and print out their end tags until we get
297
      // the node on the stack to match the currentNode parentnodeid
298
      //
299
      // So, this of course means that we rely on the list of elements
300
      // having been sorted in a depth first traversal of the nodes, which
301
      // is handled by the NodeComparator class used by the TreeSet
302
      if (!atRootElement) {
303
        NodeRecord currentElement = (NodeRecord)openElements.peek();
304
        if ( currentNode.parentnodeid != currentElement.nodeid ) {
305
          while ( currentNode.parentnodeid != currentElement.nodeid ) {
306
            currentElement = (NodeRecord)openElements.pop();
307
            util.debugMessage("\n POPPED: " + currentElement.nodename);
308
            if (previousNodeWasElement) {
309
              out.print(">");
310
              previousNodeWasElement = false;
311
            }  
312
            out.print("</" + currentElement.nodename + ">" );
313
            currentElement = (NodeRecord)openElements.peek();
314
          }
315
        }
316
      }
317

    
318
      // Handle the DOCUMENT node
319
      if (currentNode.nodetype.equals("DOCUMENT")) {
320
        out.println("<?xml version=\"1.0\"?>");
321
      
322
        if (docname != null) {
323
          if ((doctype != null) && (system_id != null)) {
324
            out.println("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
325
                       "\" \"" + system_id + "\">");
326
          } else {
327
            out.println("<!DOCTYPE " + docname + ">");
328
          }
329
        }
330

    
331
      // Handle the ELEMENT nodes
332
      } else if (currentNode.nodetype.equals("ELEMENT")) {
333
        if (atRootElement) {
334
          atRootElement = false;
335
        } else {
336
          if (previousNodeWasElement) {
337
            out.print(">");
338
          }
339
        }
340
        openElements.push(currentNode);
341
        util.debugMessage("\n PUSHED: " + currentNode.nodename);
342
        previousNodeWasElement = true;
343
        out.print("<" + currentNode.nodename);
344

    
345
      // Handle the ATTRIBUTE nodes
346
      } else if (currentNode.nodetype.equals("ATTRIBUTE")) {
347
        out.print(" " + currentNode.nodename + "=\""
348
                 + currentNode.nodedata + "\"");
349
      } else if (currentNode.nodetype.equals("TEXT")) {
350
        if (previousNodeWasElement) {
351
          out.print(">");
352
        }
353
        out.print(currentNode.nodedata);
354
        previousNodeWasElement = false;
355

    
356
      // Handle the COMMENT nodes
357
      } else if (currentNode.nodetype.equals("COMMENT")) {
358
        if (previousNodeWasElement) {
359
          out.print(">");
360
        }
361
        out.print("<!--" + currentNode.nodedata + "-->");
362
        previousNodeWasElement = false;
363

    
364
      // Handle the PI nodes
365
      } else if (currentNode.nodetype.equals("PI")) {
366
        if (previousNodeWasElement) {
367
          out.print(">");
368
        }
369
        out.print("<?" + currentNode.nodename + " " +
370
                        currentNode.nodedata + "?>");
371
        previousNodeWasElement = false;
372

    
373
      // Handle any other node type (do nothing)
374
      } else {
375
        // Any other types of nodes are not handled.
376
        // Probably should throw an exception here to indicate this
377
      }
378
      out.flush();
379
    }
380

    
381
    // Print the final end tag for the root element
382
    NodeRecord currentElement = (NodeRecord)openElements.pop();
383
    util.debugMessage("\n POPPED: " + currentElement.nodename);
384
    out.print("</" + currentElement.nodename + ">" );
385
    out.flush();
386
  }
387

    
388
  /**
389
   * Look up the document type information from the database
390
   *
391
   * @param docid the id of the document to look up
392
   */
393
  private void getDocumentInfo(String docid) throws McdbException 
394
  {
395
    PreparedStatement pstmt;
396

    
397
    try {
398
      pstmt =
399
        conn.prepareStatement("SELECT docname, doctype, rootnodeid,doctitle, " +
400
                              "date_created, date_updated, " + 
401
                              "user_owner, user_updated, server_location, " +
402
                              "public_access " +
403
                               "FROM xml_documents " +
404
                               "WHERE docid LIKE ?");
405
      // Bind the values to the query
406
      pstmt.setString(1, docid);
407

    
408
      pstmt.execute();
409
      ResultSet rs = pstmt.getResultSet();
410
      boolean tableHasRows = rs.next();
411
      if (tableHasRows) {
412
        this.docname        = rs.getString(1);
413
        this.doctype        = rs.getString(2);
414
        this.rootnodeid     = rs.getLong(3);
415
        this.doctitle       = rs.getString(4);
416
        this.createdate     = rs.getString(5);
417
        this.updatedate     = rs.getString(6);
418
        this.userowner      = rs.getString(7);
419
        this.userupdated    = rs.getString(8);
420
        this.serverlocation = rs.getInt(9);
421
        this.publicaccess   = rs.getInt(10);
422
      } 
423
      pstmt.close();
424

    
425
      if (this.doctype != null) {
426
        pstmt =
427
          conn.prepareStatement("SELECT system_id " +
428
                                  "FROM xml_catalog " +
429
                                 "WHERE public_id LIKE ?");
430
        // Bind the values to the query
431
        pstmt.setString(1, doctype);
432
  
433
        pstmt.execute();
434
        rs = pstmt.getResultSet();
435
        tableHasRows = rs.next();
436
        if (tableHasRows) {
437
          this.system_id  = rs.getString(1);
438
        } 
439
        pstmt.close();
440
      }
441
    } catch (SQLException e) {
442
      throw new McdbException("Error accessing database connection.", e);
443
    }
444

    
445
    if (this.docname == null) {
446
      throw new McdbDocNotFoundException("Document not found: " + docid);
447
    }
448
  }
449

    
450
  /**
451
   * Look up the node data from the database
452
   *
453
   * @param rootnodeid the id of the root node of the node tree to look up
454
   */
455
  private TreeSet getNodeRecordList(long rootnodeid) throws McdbException 
456
  {
457
    PreparedStatement pstmt;
458
    TreeSet nodeRecordList = new TreeSet(new NodeComparator());
459
    long nodeid = 0;
460
    long parentnodeid = 0;
461
    long nodeindex = 0;
462
    String nodetype = null;
463
    String nodename = null;
464
    String nodedata = null;
465

    
466
    try {
467
      pstmt =
468
      conn.prepareStatement("SELECT nodeid,parentnodeid,nodeindex, " +
469
           "nodetype,nodename,"+               
470
           "replace(" +
471
           "replace(" +
472
           "replace(nodedata,'&','&amp;') " +
473
           ",'<','&lt;') " +
474
           ",'>','&gt;') " +
475
           "FROM xml_nodes WHERE rootnodeid = ?");
476

    
477
      // Bind the values to the query
478
      pstmt.setLong(1, rootnodeid);
479

    
480
      pstmt.execute();
481
      ResultSet rs = pstmt.getResultSet();
482
      boolean tableHasRows = rs.next();
483
      while (tableHasRows) {
484
        nodeid = rs.getLong(1);
485
        parentnodeid = rs.getLong(2);
486
        nodeindex = rs.getLong(3);
487
        nodetype = rs.getString(4);
488
        nodename = rs.getString(5);
489
        nodedata = rs.getString(6);
490

    
491
        // add the data to the node record list hashtable
492
        NodeRecord currentRecord = new NodeRecord(nodeid, parentnodeid, 
493
                                   nodeindex, nodetype, nodename, nodedata);
494
        nodeRecordList.add(currentRecord);
495

    
496
        // Advance to the next node
497
        tableHasRows = rs.next();
498
      } 
499
      pstmt.close();
500

    
501
    } catch (SQLException e) {
502
      throw new McdbException("Error accessing database connection.", e);
503
    }
504

    
505
    if (nodeRecordList != null) {
506
      return nodeRecordList;
507
    } else {
508
      throw new McdbException("Error getting node data: " + docid);
509
    }
510
  }
511
  
512
  /** creates SQL code and inserts new document into DB connection 
513
   default serverCode of 1*/
514
  private void writeDocumentToDB(String action, String user)
515
               throws SQLException, Exception
516
  {
517
    writeDocumentToDB(action, user, 1);
518
  }
519

    
520
 /** creates SQL code and inserts new document into DB connection */
521
  private void writeDocumentToDB(String action, String user, int serverCode) 
522
               throws SQLException, Exception {
523
    try {
524
      PreparedStatement pstmt = null;
525

    
526
      if (action.equals("INSERT")) {
527
        //AccessionNumber ac = new AccessionNumber();
528
        //this.docid = ac.generate(docid, "INSERT");
529
        pstmt = conn.prepareStatement(
530
            "INSERT INTO xml_documents " +
531
            "(docid, rootnodeid, docname, doctype, user_owner, " +
532
            "user_updated, date_created, date_updated, server_location) " +
533
            "VALUES (?, ?, ?, ?, ?, ?, sysdate, sysdate, ?)");
534
        //note that the server_location is set to 1. 
535
        //this means that "localhost" in the xml_replication table must
536
        //always be the first entry!!!!!
537
        
538
        // Bind the values to the query
539
        pstmt.setString(1, this.docid);
540
        pstmt.setLong(2, rootnodeid);
541
        pstmt.setString(3, docname);
542
        pstmt.setString(4, doctype);
543
        pstmt.setString(5, user);
544
        pstmt.setString(6, user);
545
        pstmt.setInt(7, serverCode);
546
      } else if (action.equals("UPDATE")) {
547

    
548
        // Save the old document entry in a backup table
549
        DocumentImpl.archiveDocRevision( conn, docid, user );
550

    
551
        // Delete index for the old version of docid
552
        // The new index is inserting on the next calls to DBSAXNode
553
        pstmt = conn.prepareStatement(
554
                "DELETE FROM xml_index WHERE docid='" + this.docid + "'");
555
        pstmt.execute();
556
        pstmt.close();
557

    
558
        // Update the new document to reflect the new node tree
559
        pstmt = conn.prepareStatement(
560
            "UPDATE xml_documents " +
561
            "SET rootnodeid = ?, docname = ?, doctype = ?, " +
562
            "user_updated = ?, date_updated = sysdate, " +
563
            "server_location = ? WHERE docid LIKE ?");
564
        // Bind the values to the query
565
        pstmt.setLong(1, rootnodeid);
566
        pstmt.setString(2, docname);
567
        pstmt.setString(3, doctype);
568
        pstmt.setString(4, user);
569
        pstmt.setInt(5, serverCode);
570
        pstmt.setString(6, this.docid);
571
      } else {
572
        System.err.println("Action not supported: " + action);
573
      }
574

    
575
      // Do the insertion
576
      pstmt.execute();
577
      pstmt.close();
578

    
579
    } catch (SQLException sqle) {
580
      throw sqle;
581
//    } catch (AccessionNumberException ane) {
582
//      MetaCatUtil.debugMessage("Invalid accession number.");
583
//      MetaCatUtil.debugMessage(ane.getMessage());
584
//      throw ane;
585
    } catch (Exception e) {
586
      throw e;
587
    }
588
  }
589

    
590
  /**
591
   * Get the document title
592
   */
593
  public String getTitle() {
594
    return doctitle;
595
  }
596

    
597
  /**
598
   * Set the document title
599
   *
600
   * @param title the new title for the document
601
   */
602

    
603
  public void setTitle( String title ) {
604
    this.doctitle = title;
605
    try {
606
      PreparedStatement pstmt;
607
      pstmt = conn.prepareStatement(
608
            "UPDATE xml_documents " +
609
            " SET doctitle = ? " +
610
            "WHERE docid = ?");
611

    
612
      // Bind the values to the query
613
      pstmt.setString(1, doctitle);
614
      pstmt.setString(2, docid);
615

    
616
      // Do the insertion
617
      pstmt.execute();
618
      pstmt.close();
619
    } catch (SQLException e) {
620
      System.out.println(e.getMessage());
621
    }
622
  }
623

    
624
  /**
625
   * Look up the title of the first child element named "title"
626
   * and record it as the document title
627
   */
628
/*   NOT NEEDED ANY MORE
629
  public void setTitleFromChildElement() {
630
    String title = null;
631
    long assigned_id=0;
632
    PreparedStatement pstmt;
633
    try {
634
      pstmt = conn.prepareStatement(
635
              "SELECT nodedata FROM xml_nodes " +
636
              "WHERE nodetype = 'TEXT' " +
637
              "AND rootnodeid = ? " +
638
              "AND parentnodeid IN " +
639
              "  (SELECT nodeid FROM xml_nodes " +
640
              "  WHERE nodename = 'title' " +
641
              "  AND nodetype =  'ELEMENT' " +
642
              "  AND rootnodeid = ? ) " +
643
              "ORDER BY nodeid");
644
*/
645
      // The above query might be slow, and probably will be because
646
      // it gets ALL of the title elements while searching for one
647
      // title in a small subtree but it avoids the problem of using
648
      // Oracle's Hierarchical Query syntax which is not portable --
649
      // the commented out SQL that follows shows an equivalent query
650
      // using Oracle-specific hierarchical query
651
      /*
652
      pstmt = conn.prepareStatement(
653
              "SELECT nodedata FROM xml_nodes " +
654
              "WHERE nodetype = 'TEXT' " +
655
              "AND parentnodeid IN " +
656
              "(SELECT nodeid FROM xml_nodes " +
657
              "WHERE nodename = 'title' " +
658
              "START WITH nodeid = ? " +
659
              "CONNECT BY PRIOR nodeid = parentnodeid)");
660
      */
661
/*
662
      // Bind the values to the query
663
      pstmt.setLong(1, rootnodeid);
664
      pstmt.setLong(2, rootnodeid);
665

    
666
      pstmt.execute();
667
      ResultSet rs = pstmt.getResultSet();
668
      boolean tableHasRows = rs.next();
669
      if (tableHasRows) {
670
        title = rs.getString(1);
671
      }
672
      pstmt.close();
673
    } catch (SQLException e) {
674
      System.out.println("Error getting title: " + e.getMessage());
675
    }
676

    
677
    // assign the new title
678
    this.setTitle(title);
679
  }
680
*/
681

    
682
  /**
683
   * Write an XML file to the database, given a filename
684
   *
685
   * @param conn the JDBC connection to the database
686
   * @param filename the filename to be loaded into the database
687
   * @param action the action to be performed (INSERT OR UPDATE)
688
   * @param docid the docid to use for the INSERT OR UPDATE
689
   */
690
  public static String write( Connection conn, String filename,
691
                              String aclfilename, String action, String docid,
692
                              String user, String group )
693
                throws Exception {
694

    
695
    return write ( conn, new FileReader(new File(filename).toString()),
696
                   new FileReader(new File(aclfilename).toString()),
697
                   action, docid, user, group);
698
  }
699
  
700
  public static String write( Connection conn, Reader xml, Reader acl, 
701
                              String action, String docid, String user,
702
                              String group )
703
                throws Exception {
704
    return write(conn, xml, acl, action, docid, user, group, 1);
705
  }
706
  
707
  public static String write( Connection conn, Reader xml,
708
                              String action, String docid, String user,
709
                              String group, int serverCode )
710
                              throws Exception
711
  {
712
    return write(conn, xml, null,
713
                              action, docid, user,
714
                              group, serverCode);
715
  }
716
  
717
  /**
718
   * Write an XML file to the database, given a Reader
719
   *
720
   * @param conn the JDBC connection to the database
721
   * @param xml the xml stream to be loaded into the database
722
   * @param action the action to be performed (INSERT OR UPDATE)
723
   * @param docid the docid to use for the INSERT OR UPDATE
724
   */
725

    
726
  public static String write( Connection conn, Reader xml, Reader acl,
727
                              String action, String docid, String user,
728
                              String group, int serverCode )
729
                throws Exception {
730
System.out.println("outside of if: action: " + action + "servercode: " + serverCode);
731
    if(serverCode != 1 && action.equals("UPDATE"))
732
    { //if this document being written is not a resident of this server then
733
      //we need to try to get a lock from it's resident server.  If the
734
      //resident server will not give a lock then we send the user a message
735
      //saying that he/she needs to download a new copy of the file and
736
      //merge the differences manually.
737
System.out.println("in if: action: " + action + "servercode: " + serverCode);
738
      int istreamInt;
739
      char istreamChar;
740
      DocumentImpl newdoc = new DocumentImpl(conn, docid);
741
      String update = newdoc.getUpdateDate();
742
      String server = MetacatReplication.getServer(serverCode);
743
      
744
      URL u = new URL("http://" + server + "?action=getlock&updatedate=" + update +
745
                  "&docid=" + docid);
746
      InputStreamReader istream = new InputStreamReader(u.openStream());
747
      StringBuffer serverResponse = new StringBuffer();
748
      while((istreamInt = istream.read()) != -1)
749
      {
750
        istreamChar = (char)istreamInt;
751
        serverResponse.append(istreamChar);
752
      }
753
      
754
      String serverResStr = serverResponse.toString();
755
      String openingtag = serverResStr.substring(0, serverResStr.indexOf(">"));
756
      System.out.println("openingtag: " + openingtag);
757
      if(openingtag.equals("<lockgranted>"))
758
      {//the lock was granted go ahead with the insert
759
        
760
      }
761
      else if(openingtag.equals("<filelocked>"))
762
      {//the file is currently locked by another user
763
       //notify our user to wait a few minutes, check out a new copy and try
764
       //again.
765
        
766
      }
767
      else if(openingtag.equals("<outdatedfile>"))
768
      {//our file is outdated.  notify our user to check out a new copy of the
769
       //file and merge his version with the new version.
770
        
771
      }
772
    }
773
    
774
    // Determine if the docid is OK for INSERT or UPDATE
775
    AccessionNumber ac = new AccessionNumber(conn);
776
    String newdocid = ac.generate(docid, action);
777

    
778
    if ( action.equals("UPDATE") ) {
779
      // check for 'write' permission for 'user' to update this document
780
      if ( !hasWritePermission(conn, docid, user, group) ) {
781
        throw new Exception("User " + user + 
782
              " does not have permission to update XML Document #" + docid);
783
      }          
784
    }
785

    
786
    try {
787
      XMLReader parser = initializeParser(conn,action,newdocid,user,serverCode);
788
      conn.setAutoCommit(false);
789
      parser.parse(new InputSource(xml));
790
      conn.commit();
791
      if ( acl != null ) {
792
        if ( action.equals("UPDATE") ) {
793
          Statement stmt = conn.createStatement();
794
          stmt.execute("DELETE FROM xml_access WHERE docid='"+newdocid +"'");
795
          stmt.close();
796
        }
797
        AccessControlList aclobj = new AccessControlList(conn, newdocid, acl);
798
        conn.commit();
799
      }
800
      conn.setAutoCommit(true);
801
//    } catch (SAXParseException e) {
802
//      conn.rollback();
803
//      conn.setAutoCommit(true);
804
//      throw e;
805
//    } catch (SAXException e) {
806
//      conn.rollback();
807
//      conn.setAutoCommit(true);
808
//      throw e;
809
    } catch (Exception e) {
810
      conn.rollback();
811
      conn.setAutoCommit(true);
812
      throw e;
813
    }
814
      
815
    if ( (docid != null) && !(newdocid.equals(docid)) ) {
816
      return new String("New document ID generated:" + newdocid);
817
    } else {
818
      return newdocid;
819
    }
820
  }
821

    
822
  /**
823
   * Delete an XML file from the database (actually, just make it a revision
824
   * in the xml_revisions table)
825
   *
826
   * @param docid the ID of the document to be deleted from the database
827
   */
828
  public static void delete( Connection conn, String docid,
829
                                 String user, String group )
830
                throws Exception {
831

    
832
    // Determine if the docid is OK for DELETE
833
    AccessionNumber ac = new AccessionNumber(conn);
834
    String newdocid = ac.generate(docid, "DELETE");
835

    
836
    // check for 'write' permission for 'user' to delete this document
837
    if ( !hasWritePermission(conn, docid, user, group) ) {
838
      throw new Exception("User " + user + 
839
              " does not have permission to delete XML Document #" + docid);
840
    }
841

    
842
    conn.setAutoCommit(false);
843
    // Copy the record to the xml_revisions table
844
    DocumentImpl.archiveDocRevision( conn, docid, user );
845

    
846
    // Now delete it from the xml_documents table
847
    Statement stmt = conn.createStatement();
848
    stmt.execute("DELETE FROM xml_index WHERE docid = '" + docid + "'");
849
    stmt.execute("DELETE FROM xml_documents WHERE docid = '" + docid + "'");
850
    stmt.close();
851
    conn.commit();
852
    conn.setAutoCommit(true);
853
  }
854
  
855
  /** Check for "write" permissions from DB connection */
856
  private static boolean hasWritePermission(Connection conn, String docid, 
857
                                     String user, String group) 
858
                                     throws SQLException {
859
    // b' of the command line invocation
860
    if ( (user == null) && (group == null) ) {
861
      return true;
862
    }
863

    
864
    PreparedStatement pstmt;
865
    // checking if user is owner of docid
866
    try {
867
      pstmt = conn.prepareStatement(
868
                   "SELECT 'x' FROM xml_documents " +
869
                   "WHERE docid LIKE ? AND user_owner LIKE ?");
870
      // Bind the values to the query
871
      pstmt.setString(1, docid);
872
      pstmt.setString(2, user);
873

    
874
      pstmt.execute();
875
      ResultSet rs = pstmt.getResultSet();
876
      boolean hasRow = rs.next();
877
      pstmt.close();
878
      if (hasRow) {
879
        return true;
880
      }
881
      
882
    } catch (SQLException e) {
883
      throw new 
884
        SQLException("Error checking document's owner: " + e.getMessage());
885
    }
886

    
887
    // checking access type from xml_access table
888
    int accesstype = 0;
889
    try {
890
      pstmt = conn.prepareStatement(
891
                   "SELECT access_type FROM xml_access " +
892
                   "WHERE docid LIKE ? " + 
893
                   "AND principal_name LIKE ? " +
894
                   "AND principal_type = 'user' " +
895
                   "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
896
                                   "AND nvl(end_time,sysdate) " +
897
                   "UNION " +
898
                   "SELECT access_type FROM xml_access " +
899
                   "WHERE docid LIKE ? " + 
900
                   "AND principal_name LIKE ? " +
901
                   "AND principal_type = 'group' " +
902
                   "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
903
                                   "AND nvl(end_time,sysdate)");
904
      // Bind the values to the query
905
      pstmt.setString(1, docid);
906
      pstmt.setString(2, user);
907
      pstmt.setString(3, docid);
908
      pstmt.setString(4, group);
909

    
910
      pstmt.execute();
911
      ResultSet rs = pstmt.getResultSet();
912
      boolean hasRows = rs.next();
913
      while ( hasRows ) {
914
        accesstype = rs.getInt(1);
915
        if ( (accesstype & WRITE) == WRITE ) {
916
          pstmt.close();
917
          return true;
918
        }
919
        hasRows = rs.next();
920
      }
921

    
922
      pstmt.close();
923
      return false;
924
      
925
    } catch (SQLException e) {
926
      throw new 
927
      SQLException("Error getting document's permissions: " + e.getMessage());
928
    }
929
  }
930

    
931
  /**
932
   * Set up the parser handlers for writing the document to the database
933
   */
934
  private static XMLReader initializeParser(Connection conn,
935
                           String action, String docid, String user, int serverCode) 
936
                           throws Exception {
937
    XMLReader parser = null;
938
    //
939
    // Set up the SAX document handlers for parsing
940
    //
941
    try {
942
      ContentHandler chandler   = new DBSAXHandler(conn, action, docid, user, serverCode);
943
      EntityResolver dbresolver = new DBEntityResolver(conn, 
944
                                      (DBSAXHandler)chandler);
945
      DTDHandler dtdhandler     = new DBDTDHandler(conn);
946

    
947
      // Get an instance of the parser
948
      MetaCatUtil util = new MetaCatUtil();
949
      String parserName = util.getOption("saxparser");
950
      parser = XMLReaderFactory.createXMLReader(parserName);
951

    
952
      // Turn off validation
953
      parser.setFeature("http://xml.org/sax/features/validation", false);
954
      
955
      // Set Handlers in the parser
956
      parser.setProperty("http://xml.org/sax/properties/declaration-handler",
957
                         chandler);
958
      parser.setProperty("http://xml.org/sax/properties/lexical-handler",
959
                         chandler);
960
      parser.setContentHandler((ContentHandler)chandler);
961
      parser.setEntityResolver((EntityResolver)dbresolver);
962
      parser.setDTDHandler((DTDHandler)dtdhandler);
963
      parser.setErrorHandler((ErrorHandler)chandler);
964

    
965
    } catch (Exception e) {
966
      throw e;
967
    }
968

    
969
    return parser;
970
  }
971

    
972
  /** Save a document entry in the xml_revisions table */
973
  private static void archiveDocRevision(Connection conn, String docid,
974
                                         String user) 
975
                                         throws SQLException {
976
    // create a record in xml_revisions table 
977
    // for that document as selected from xml_documents
978
    PreparedStatement pstmt = conn.prepareStatement(
979
      "INSERT INTO xml_revisions " +
980
        "(revisionid, docid, rootnodeid, docname, doctype, doctitle, " +
981
        "user_owner, user_updated, date_created, date_updated, server_location) " +
982
      "SELECT null, ?, rootnodeid, docname, doctype, doctitle," + 
983
        "user_owner, ?, sysdate, sysdate, server_location "+
984
      "FROM xml_documents " +
985
      "WHERE docid = ?");
986
    // Bind the values to the query and execute it
987
    pstmt.setString(1, docid);
988
    pstmt.setString(2, user);
989
    pstmt.setString(3, docid);
990
    pstmt.execute();
991
    pstmt.close();
992

    
993
  }
994

    
995
  /**
996
   * the main routine used to test the DBWriter utility.
997
   * <p>
998
   * Usage: java DocumentImpl <-f filename -a action -d docid>
999
   *
1000
   * @param filename the filename to be loaded into the database
1001
   * @param action the action to perform (READ, INSERT, UPDATE, DELETE)
1002
   * @param docid the id of the document to process
1003
   */
1004
  static public void main(String[] args) {
1005
     
1006
    try {
1007
      String filename    = null;
1008
      String aclfilename = null;
1009
      String action      = null;
1010
      String docid       = null;
1011
      boolean showRuntime = false;
1012
      boolean useOldReadAlgorithm = false;
1013

    
1014
      // Parse the command line arguments
1015
      for ( int i=0 ; i < args.length; ++i ) {
1016
        if ( args[i].equals( "-f" ) ) {
1017
          filename =  args[++i];
1018
        } else if ( args[i].equals( "-c" ) ) {
1019
          aclfilename =  args[++i];
1020
        } else if ( args[i].equals( "-a" ) ) {
1021
          action =  args[++i];
1022
        } else if ( args[i].equals( "-d" ) ) {
1023
          docid =  args[++i];
1024
        } else if ( args[i].equals( "-t" ) ) {
1025
          showRuntime = true;
1026
        } else if ( args[i].equals( "-old" ) ) {
1027
          useOldReadAlgorithm = true;
1028
        } else {
1029
          System.err.println
1030
            ( "   args[" +i+ "] '" +args[i]+ "' ignored." );
1031
        }
1032
      }
1033
      
1034
      // Check if the required arguments are provided
1035
      boolean argsAreValid = false;
1036
      if (action != null) {
1037
        if (action.equals("INSERT")) {
1038
          if (filename != null) {
1039
            argsAreValid = true;
1040
          } 
1041
        } else if (action.equals("UPDATE")) {
1042
          if ((filename != null) && (docid != null)) {
1043
            argsAreValid = true;
1044
          } 
1045
        } else if (action.equals("DELETE")) {
1046
          if (docid != null) {
1047
            argsAreValid = true;
1048
          } 
1049
        } else if (action.equals("READ")) {
1050
          if (docid != null) {
1051
            argsAreValid = true;
1052
          } 
1053
        } 
1054
      } 
1055

    
1056
      // Print usage message if the arguments are not valid
1057
      if (!argsAreValid) {
1058
        System.err.println("Wrong number of arguments!!!");
1059
        System.err.println(
1060
          "USAGE: java DocumentImpl [-t] <-a INSERT> [-d docid] <-f filename> [-c aclfilename]");
1061
        System.err.println(
1062
          "   OR: java DocumentImpl [-t] <-a UPDATE -d docid -f filename> [-c aclfilename]");
1063
        System.err.println(
1064
          "   OR: java DocumentImpl [-t] <-a DELETE -d docid>");
1065
        System.err.println(
1066
          "   OR: java DocumentImpl [-t] [-old] <-a READ -d docid>");
1067
        return;
1068
      }
1069
      
1070
      // Time the request if asked for
1071
      double startTime = System.currentTimeMillis();
1072
      
1073
      // Open a connection to the database
1074
      MetaCatUtil util = new MetaCatUtil();
1075
      Connection dbconn = util.openDBConnection();
1076

    
1077
      double connTime = System.currentTimeMillis();
1078
      // Execute the action requested (READ, INSERT, UPDATE, DELETE)
1079
      if (action.equals("READ")) {
1080
          DocumentImpl xmldoc = new DocumentImpl( dbconn, docid );
1081
          if (useOldReadAlgorithm) {
1082
            System.out.println(xmldoc.readUsingSlowAlgorithm());
1083
          } else {
1084
            xmldoc.toXml(new PrintWriter(System.out));
1085
          }
1086
      } else if (action.equals("DELETE")) {
1087
        DocumentImpl.delete(dbconn, docid, null, null);
1088
        System.out.println("Document deleted: " + docid);
1089
      } else {
1090
        String newdocid = DocumentImpl.write(dbconn, filename, aclfilename,
1091
                                             action, docid, null, null);
1092
        if ((docid != null) && (!docid.equals(newdocid))) {
1093
          if (action.equals("INSERT")) {
1094
            System.out.println("New document ID generated!!! ");
1095
          } else if (action.equals("UPDATE")) {
1096
            System.out.println("ERROR: Couldn't update document!!! ");
1097
          }
1098
        } else if ((docid == null) && (action.equals("UPDATE"))) {
1099
          System.out.println("ERROR: Couldn't update document!!! ");
1100
        }
1101
        System.out.println("Document processing finished for: " + filename
1102
              + " (" + newdocid + ")");
1103
      }
1104

    
1105
      double stopTime = System.currentTimeMillis();
1106
      double dbOpenTime = (connTime - startTime)/1000;
1107
      double insertTime = (stopTime - connTime)/1000;
1108
      double executionTime = (stopTime - startTime)/1000;
1109
      if (showRuntime) {
1110
        System.out.println("\n\nTotal Execution time was: " + 
1111
                           executionTime + " seconds.");
1112
        System.out.println("Time to open DB connection was: " + dbOpenTime + 
1113
                           " seconds.");
1114
        System.out.println("Time to insert document was: " + insertTime +
1115
                           " seconds.");
1116
      }
1117
      dbconn.close();
1118
    } catch (McdbException me) {
1119
      me.toXml(new PrintWriter(System.err));
1120
    } catch (AccessionNumberException ane) {
1121
      System.out.println("ERROR: Couldn't delete document!!! ");
1122
      System.out.println(ane.getMessage());
1123
    } catch (Exception e) {
1124
      System.err.println("EXCEPTION HANDLING REQUIRED");
1125
      System.err.println(e.getMessage());
1126
      e.printStackTrace(System.err);
1127
    }
1128
  }
1129
}
(20-20/35)