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-12-01 15:27:58 -0800 (Fri, 01 Dec 2000) $'
11
 * '$Revision: 577 $'
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
import java.util.Enumeration;
30

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

    
42
import java.net.URL;
43

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

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

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

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

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

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

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

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

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

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

    
220
  /**
221
   * Print a string representation of the XML document
222
   */
223
  public String toString()
224
  {
225
    StringWriter docwriter = new StringWriter();
226
    this.toXml(docwriter);
227
    String document = docwriter.toString();
228
    return document;
229
  }
230

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

    
245
    // Create the elements from the downloaded data in the TreeSet
246
    rootNode = new ElementNode(nodeRecordList, rootnodeid);
247

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

    
264
  /**
265
   * Print a text representation of the XML document to a Writer
266
   *
267
   * @param pw the Writer to which we print the document
268
   */
269
  public void toXml(Writer pw)
270
  {
271
    PrintWriter out = null;
272
    if (pw instanceof PrintWriter) {
273
      out = (PrintWriter)pw;
274
    } else {
275
      out = new PrintWriter(pw);
276
    }
277

    
278
    MetaCatUtil util = new MetaCatUtil();
279
    
280
    Stack openElements = new Stack();
281
    boolean atRootElement = true;
282
    boolean previousNodeWasElement = false;
283

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

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

    
324
      // Handle the DOCUMENT node
325
      if (currentNode.nodetype.equals("DOCUMENT")) {
326
        out.println("<?xml version=\"1.0\"?>");
327
      
328
        if (docname != null) {
329
          if ((doctype != null) && (system_id != null)) {
330
            out.println("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
331
                       "\" \"" + system_id + "\">");
332
          } else {
333
            out.println("<!DOCTYPE " + docname + ">");
334
          }
335
        }
336

    
337
      // Handle the ELEMENT nodes
338
      } else if (currentNode.nodetype.equals("ELEMENT")) {
339
        if (atRootElement) {
340
          atRootElement = false;
341
        } else {
342
          if (previousNodeWasElement) {
343
            out.print(">");
344
          }
345
        }
346
        openElements.push(currentNode);
347
        util.debugMessage("\n PUSHED: " + currentNode.nodename);
348
        previousNodeWasElement = true;
349
        out.print("<" + currentNode.nodename);
350

    
351
      // Handle the ATTRIBUTE nodes
352
      } else if (currentNode.nodetype.equals("ATTRIBUTE")) {
353
        out.print(" " + currentNode.nodename + "=\""
354
                 + currentNode.nodedata + "\"");
355
      } else if (currentNode.nodetype.equals("TEXT")) {
356
        if (previousNodeWasElement) {
357
          out.print(">");
358
        }
359
        out.print(currentNode.nodedata);
360
        previousNodeWasElement = false;
361

    
362
      // Handle the COMMENT nodes
363
      } else if (currentNode.nodetype.equals("COMMENT")) {
364
        if (previousNodeWasElement) {
365
          out.print(">");
366
        }
367
        out.print("<!--" + currentNode.nodedata + "-->");
368
        previousNodeWasElement = false;
369

    
370
      // Handle the PI nodes
371
      } else if (currentNode.nodetype.equals("PI")) {
372
        if (previousNodeWasElement) {
373
          out.print(">");
374
        }
375
        out.print("<?" + currentNode.nodename + " " +
376
                        currentNode.nodedata + "?>");
377
        previousNodeWasElement = false;
378

    
379
      // Handle any other node type (do nothing)
380
      } else {
381
        // Any other types of nodes are not handled.
382
        // Probably should throw an exception here to indicate this
383
      }
384
      out.flush();
385
    }
386

    
387
    // Print the final end tag for the root element
388
    NodeRecord currentElement = (NodeRecord)openElements.pop();
389
    util.debugMessage("\n POPPED: " + currentElement.nodename);
390
    out.print("</" + currentElement.nodename + ">" );
391
    out.flush();
392
  }
393

    
394
  /**
395
   * Look up the document type information from the database
396
   *
397
   * @param docid the id of the document to look up
398
   */
399
  private void getDocumentInfo(String docid) throws McdbException 
400
  {
401
    PreparedStatement pstmt;
402

    
403
    try {
404
      pstmt =
405
        conn.prepareStatement("SELECT docname, doctype, rootnodeid,doctitle, " +
406
                              "date_created, date_updated, " + 
407
                              "user_owner, user_updated, server_location, " +
408
                              "public_access, rev " +
409
                               "FROM xml_documents " +
410
                               "WHERE docid LIKE ?");
411
      // Bind the values to the query
412
      pstmt.setString(1, docid);
413

    
414
      pstmt.execute();
415
      ResultSet rs = pstmt.getResultSet();
416
      boolean tableHasRows = rs.next();
417
      if (tableHasRows) {
418
        this.docname        = rs.getString(1);
419
        this.doctype        = rs.getString(2);
420
        this.rootnodeid     = rs.getLong(3);
421
        this.doctitle       = rs.getString(4);
422
        this.createdate     = rs.getString(5);
423
        this.updatedate     = rs.getString(6);
424
        this.userowner      = rs.getString(7);
425
        this.userupdated    = rs.getString(8);
426
        this.serverlocation = rs.getInt(9);
427
        this.publicaccess   = rs.getInt(10);
428
        this.rev            = rs.getInt(11);
429
      } 
430
      pstmt.close();
431

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

    
452
    if (this.docname == null) {
453
      throw new McdbDocNotFoundException("Document not found: " + docid);
454
    }
455
  }
456

    
457
  /**
458
   * Look up the node data from the database
459
   *
460
   * @param rootnodeid the id of the root node of the node tree to look up
461
   */
462
  private TreeSet getNodeRecordList(long rootnodeid) throws McdbException 
463
  {
464
    PreparedStatement pstmt;
465
    TreeSet nodeRecordList = new TreeSet(new NodeComparator());
466
    long nodeid = 0;
467
    long parentnodeid = 0;
468
    long nodeindex = 0;
469
    String nodetype = null;
470
    String nodename = null;
471
    String nodedata = null;
472

    
473
    try {
474
      pstmt =
475
      conn.prepareStatement("SELECT nodeid,parentnodeid,nodeindex, " +
476
           "nodetype,nodename,"+               
477
           "replace(" +
478
           "replace(" +
479
           "replace(nodedata,'&','&amp;') " +
480
           ",'<','&lt;') " +
481
           ",'>','&gt;') " +
482
           "FROM xml_nodes WHERE rootnodeid = ?");
483

    
484
      // Bind the values to the query
485
      pstmt.setLong(1, rootnodeid);
486

    
487
      pstmt.execute();
488
      ResultSet rs = pstmt.getResultSet();
489
      boolean tableHasRows = rs.next();
490
      while (tableHasRows) {
491
        nodeid = rs.getLong(1);
492
        parentnodeid = rs.getLong(2);
493
        nodeindex = rs.getLong(3);
494
        nodetype = rs.getString(4);
495
        nodename = rs.getString(5);
496
        nodedata = rs.getString(6);
497

    
498
        // add the data to the node record list hashtable
499
        NodeRecord currentRecord = new NodeRecord(nodeid, parentnodeid, 
500
                                   nodeindex, nodetype, nodename, nodedata);
501
        nodeRecordList.add(currentRecord);
502

    
503
        // Advance to the next node
504
        tableHasRows = rs.next();
505
      } 
506
      pstmt.close();
507

    
508
    } catch (SQLException e) {
509
      throw new McdbException("Error accessing database connection.", e);
510
    }
511

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

    
527
 /** creates SQL code and inserts new document into DB connection */
528
  private void writeDocumentToDB(String action, String user, int serverCode) 
529
               throws SQLException, Exception {
530
    try {
531
      PreparedStatement pstmt = null;
532

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

    
555
        // Save the old document entry in a backup table
556
        DocumentImpl.archiveDocRevision( conn, docid, user );
557
        DocumentImpl thisdoc = new DocumentImpl(conn, docid);
558
        int thisrev = thisdoc.getRev();
559
        thisrev++;
560
        // Delete index for the old version of docid
561
        // The new index is inserting on the next calls to DBSAXNode
562
        pstmt = conn.prepareStatement(
563
                "DELETE FROM xml_index WHERE docid='" + this.docid + "'");
564
        pstmt.execute();
565
        pstmt.close();
566

    
567
        // Update the new document to reflect the new node tree
568
        pstmt = conn.prepareStatement(
569
            "UPDATE xml_documents " +
570
            "SET rootnodeid = ?, docname = ?, doctype = ?, " +
571
            "user_updated = ?, date_updated = sysdate, " +
572
            "server_location = ?, rev = ? WHERE docid LIKE ?");
573
        // Bind the values to the query
574
        pstmt.setLong(1, rootnodeid);
575
        pstmt.setString(2, docname);
576
        pstmt.setString(3, doctype);
577
        pstmt.setString(4, user);
578
        pstmt.setInt(5, serverCode);
579
        pstmt.setInt(6, thisrev);
580
        pstmt.setString(7, this.docid);
581

    
582
      } else {
583
        System.err.println("Action not supported: " + action);
584
      }
585

    
586
      // Do the insertion
587
      pstmt.execute();
588
      pstmt.close();
589

    
590
    } catch (SQLException sqle) {
591
      throw sqle;
592
//    } catch (AccessionNumberException ane) {
593
//      MetaCatUtil.debugMessage("Invalid accession number.");
594
//      MetaCatUtil.debugMessage(ane.getMessage());
595
//      throw ane;
596
    } catch (Exception e) {
597
      throw e;
598
    }
599
  }
600

    
601
  /**
602
   * Get the document title
603
   */
604
  public String getTitle() {
605
    return doctitle;
606
  }
607

    
608
  /**
609
   * Set the document title
610
   *
611
   * @param title the new title for the document
612
   */
613

    
614
  public void setTitle( String title ) {
615
    this.doctitle = title;
616
    try {
617
      PreparedStatement pstmt;
618
      pstmt = conn.prepareStatement(
619
            "UPDATE xml_documents " +
620
            " SET doctitle = ? " +
621
            "WHERE docid = ?");
622

    
623
      // Bind the values to the query
624
      pstmt.setString(1, doctitle);
625
      pstmt.setString(2, docid);
626

    
627
      // Do the insertion
628
      pstmt.execute();
629
      pstmt.close();
630
    } catch (SQLException e) {
631
      System.out.println(e.getMessage());
632
    }
633
  }
634

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

    
677
      pstmt.execute();
678
      ResultSet rs = pstmt.getResultSet();
679
      boolean tableHasRows = rs.next();
680
      if (tableHasRows) {
681
        title = rs.getString(1);
682
      }
683
      pstmt.close();
684
    } catch (SQLException e) {
685
      System.out.println("Error getting title: " + e.getMessage());
686
    }
687

    
688
    // assign the new title
689
    this.setTitle(title);
690
  }
691
*/
692

    
693
  /**
694
   * Write an XML file to the database, given a filename
695
   *
696
   * @param conn the JDBC connection to the database
697
   * @param filename the filename to be loaded into the database
698
   * @param action the action to be performed (INSERT OR UPDATE)
699
   * @param docid the docid to use for the INSERT OR UPDATE
700
   */
701
  public static String write( Connection conn, String filename,
702
                              String aclfilename, String action, String docid,
703
                              String user, String group )
704
                throws Exception {
705
    return write ( conn, new FileReader(new File(filename).toString()),
706
                   new FileReader(new File(aclfilename).toString()),
707
                   action, docid, user, group);
708
  }
709
  
710
  public static String write( Connection conn, Reader xml, Reader acl, 
711
                              String action, String docid, String user,
712
                              String group )
713
                throws Exception {
714
    if(action.equals("UPDATE"))
715
    {//if the document is being updated then use the servercode from the 
716
     //originally inserted document.
717
      DocumentImpl doc = new DocumentImpl(conn, docid);
718
      int servercode = doc.getServerlocation();
719
      return write(conn, xml, acl, action, docid, user, group, servercode);
720
    }
721
    else
722
    {//if the file is being inserted then the servercode is always 1
723
      return write(conn, xml, acl, action, docid, user, group, 1);
724
    }
725
  }
726
  
727
  public static String write( Connection conn, Reader xml,
728
                              String action, String docid, String user,
729
                              String group, int serverCode )
730
                              throws Exception
731
  {
732
    return write(conn, xml, null, action, docid, user, group, serverCode); 
733
  }
734
  
735
  public static String write( Connection conn, Reader xml, Reader acl,
736
                              String action, String docid, String user,
737
                              String group, int serverCode) throws Exception
738
  {
739
    return write(conn, xml, acl, action, docid, user, group, serverCode, false);
740
  }
741
  
742
  /**
743
   * Write an XML file to the database, given a Reader
744
   *
745
   * @param conn the JDBC connection to the database
746
   * @param xml the xml stream to be loaded into the database
747
   * @param action the action to be performed (INSERT OR UPDATE)
748
   * @param docid the docid to use for the INSERT OR UPDATE
749
   */
750

    
751
  public static String write( Connection conn, Reader xml, Reader acl,
752
                              String action, String docid, String user,
753
                              String group, int serverCode, boolean override)
754
                              throws Exception 
755
  {
756
    System.out.println("in write");
757
    MetaCatUtil util = new MetaCatUtil();
758
        // Determine if the docid is OK for INSERT or UPDATE
759
    AccessionNumber ac = new AccessionNumber(conn);
760
    String newdocid = ac.generate(docid, action);
761
    
762
    System.out.println("action: " + action + " servercode: " + 
763
                        serverCode + " override: " + override);
764
    if((serverCode != 1 && action.equals("UPDATE")) && !override)
765
    { //if this document being written is not a resident of this server then
766
      //we need to try to get a lock from it's resident server.  If the
767
      //resident server will not give a lock then we send the user a message
768
      //saying that he/she needs to download a new copy of the file and
769
      //merge the differences manually.
770
      int istreamInt; 
771
      char istreamChar;
772
      DocumentImpl newdoc = new DocumentImpl(conn, docid);
773
      String update = newdoc.getUpdateDate();
774
      String server = MetacatReplication.getServer(serverCode);
775
      update = update.replace(' ', '+');
776
      URL u = new URL("http://" + server + "?action=getlock&updatedate=" + 
777
                      update + "&docid=" + docid);
778
      System.out.println("sending message: " + u.toString());
779
      String serverResStr = MetacatReplication.getURLContent(u);
780
      String openingtag = serverResStr.substring(0, serverResStr.indexOf(">")+1);
781
      
782
      if(openingtag.equals("<lockgranted>"))
783
      {//the lock was granted go ahead with the insert
784
        try 
785
        {
786
          System.out.println("in try");
787
          XMLReader parser = initializeParser(conn,action,newdocid,user,
788
                                              serverCode);
789
          conn.setAutoCommit(false);
790
          parser.parse(new InputSource(xml));
791
          conn.commit();
792
          if ( acl != null ) 
793
          {
794
            if ( action.equals("UPDATE") ) 
795
            {
796
              Statement stmt = conn.createStatement();
797
              stmt.execute("DELETE FROM xml_access WHERE docid='"+newdocid +"'");
798
              stmt.close();
799
            }
800
            AccessControlList aclobj = new AccessControlList(conn, newdocid,acl);
801
            conn.commit();
802
          } 
803
          conn.setAutoCommit(true);
804
        } 
805
        catch (Exception e) 
806
        {
807
          conn.rollback();
808
          conn.setAutoCommit(true);
809
          throw e;
810
        }
811
                
812
        //after inserting the document locally, tell the document's home server
813
        //to come get a copy from here.
814
        URL comeAndGetIt = new URL("http://" + server + 
815
                                   "?action=forcereplicate&server=" + 
816
                                   util.getOption("server") + 
817
                                   util.getOption("replicationpath") +
818
                                   "&docid=" + docid);
819
        System.out.println("sending message: " + comeAndGetIt.toString());
820
        String message = MetacatReplication.getURLContent(comeAndGetIt);
821
        
822
        if ( (docid != null) && !(newdocid.equals(docid)) ) 
823
        {
824
          return new String("New document ID generated:" + newdocid);
825
        } 
826
        else 
827
        {
828
          return newdocid;
829
        }
830
      }
831
      else if(openingtag.equals("<filelocked>"))
832
      {//the file is currently locked by another user
833
       //notify our user to wait a few minutes, check out a new copy and try
834
       //again.
835
        System.out.println("file locked");
836
        throw new Exception("The file specified is already locked by another " +
837
                            "user.  Please wait 30 seconds, checkout the " +
838
                            "newer document, merge your changes and try " +
839
                            "again.");
840
      }
841
      else if(openingtag.equals("<outdatedfile>"))
842
      {//our file is outdated.  notify our user to check out a new copy of the
843
       //file and merge his version with the new version.
844
        System.out.println("outdated file");
845
        throw new Exception("The file you are trying to update is an outdated" +
846
                            " version.  Please checkout the newest document, " +
847
                            "merge your changes and try again.");
848
      }
849
    }
850
    
851
    if ( action.equals("UPDATE") ) {
852
      // check for 'write' permission for 'user' to update this document
853
      if ( !hasPermission(conn, user, group, docid) ) {
854
        throw new Exception("User " + user + 
855
              " does not have permission to update XML Document #" + docid);
856
      }          
857
    }
858

    
859
    try 
860
    {
861
      XMLReader parser = initializeParser(conn,action,newdocid,user,serverCode);
862
      conn.setAutoCommit(false);
863
      parser.parse(new InputSource(xml));
864
      conn.commit();
865
      if ( acl != null ) 
866
      {
867
        if ( action.equals("UPDATE") )  
868
        {
869
          Statement stmt = conn.createStatement();
870
          stmt.execute("DELETE FROM xml_access WHERE docid='"+newdocid +"'");
871
          stmt.close();
872
        }
873
        AccessControlList aclobj = new AccessControlList(conn, newdocid, acl);
874
        conn.commit();
875
      }
876
      conn.setAutoCommit(true);
877

    
878
    } 
879
    catch (Exception e) 
880
    {
881
      conn.rollback();
882
      conn.setAutoCommit(true);
883
      throw e;
884
    }
885
    
886
    //force replicate out the new document to each server in our server list.
887
    if(serverCode == 1)
888
    {
889
      Enumeration keys = (ReplicationHandler.buildServerList(conn)).keys();
890
      while(keys.hasMoreElements())
891
      {
892
        String server = (String)(keys.nextElement());
893
        URL comeAndGetIt = new URL("http://" + server + 
894
                                   "?action=forcereplicate&server=" + 
895
                                   util.getOption("server") + 
896
                                   util.getOption("replicationpath") +
897
                                   "&docid=" + newdocid + "&dbaction=" +
898
                                   action);
899
        System.out.println("sending message: " + comeAndGetIt.toString());
900
        String message = MetacatReplication.getURLContent(comeAndGetIt);
901
      }
902
    }
903
      
904
    if ( (docid != null) && !(newdocid.equals(docid)) ) 
905
    {
906
      return new String("New document ID generated:" + newdocid);
907
    } 
908
    else 
909
    {
910
      return newdocid;
911
    }
912
  }
913

    
914
  /**
915
   * Delete an XML file from the database (actually, just make it a revision
916
   * in the xml_revisions table)
917
   *
918
   * @param docid the ID of the document to be deleted from the database
919
   */
920
  public static void delete( Connection conn, String docid,
921
                                 String user, String group )
922
                throws Exception {
923

    
924
    // Determine if the docid is OK for DELETE
925
    AccessionNumber ac = new AccessionNumber(conn);
926
    String newdocid = ac.generate(docid, "DELETE");
927

    
928
    // check for 'write' permission for 'user' to delete this document
929
    if ( !hasPermission(conn, user, group, docid) ) {
930
      throw new Exception("User " + user + 
931
              " does not have permission to delete XML Document #" + docid);
932
    }
933

    
934
    conn.setAutoCommit(false);
935
    // Copy the record to the xml_revisions table
936
    DocumentImpl.archiveDocRevision( conn, docid, user );
937

    
938
    // Now delete it from the xml_documents table
939
    Statement stmt = conn.createStatement();
940
    stmt.execute("DELETE FROM xml_index WHERE docid = '" + docid + "'");
941
    stmt.execute("DELETE FROM xml_documents WHERE docid = '" + docid + "'");
942
    stmt.close();
943
    conn.commit();
944
    conn.setAutoCommit(true);
945
  }
946
  
947
  /** 
948
    * Check for "WRITE" permission on @docid for @user and/or @group 
949
    * from DB connection 
950
    */
951
  private static boolean hasPermission( Connection conn, String user,
952
                                        String group, String docid) 
953
                         throws SQLException 
954
  {
955
    // b' of the command line invocation
956
    if ( (user == null) && (group == null) ) {
957
      return true;
958
    }
959

    
960
    // Check for WRITE permission on @docid for @user and/or @group
961
    AccessControlList aclobj = new AccessControlList(conn);
962
    boolean hasPermission = aclobj.hasPermission("WRITE",user,docid);
963
    if ( !hasPermission && group != null ) {
964
      hasPermission = aclobj.hasPermission("WRITE",group,docid);
965
    }
966
    
967
    return hasPermission;
968
  }
969

    
970
  /**
971
   * Set up the parser handlers for writing the document to the database
972
   */
973
  private static XMLReader initializeParser(Connection conn,
974
                           String action, String docid, String user, int serverCode) 
975
                           throws Exception {
976
    XMLReader parser = null;
977
    //
978
    // Set up the SAX document handlers for parsing
979
    //
980
    try {
981
      ContentHandler chandler   = new DBSAXHandler(conn, action, docid, user, serverCode);
982
      EntityResolver dbresolver = new DBEntityResolver(conn, 
983
                                      (DBSAXHandler)chandler);
984
      DTDHandler dtdhandler     = new DBDTDHandler(conn);
985

    
986
      // Get an instance of the parser
987
      MetaCatUtil util = new MetaCatUtil();
988
      String parserName = util.getOption("saxparser");
989
      parser = XMLReaderFactory.createXMLReader(parserName);
990

    
991
      // Turn off validation
992
      parser.setFeature("http://xml.org/sax/features/validation", false);
993
      
994
      // Set Handlers in the parser
995
      parser.setProperty("http://xml.org/sax/properties/declaration-handler",
996
                         chandler);
997
      parser.setProperty("http://xml.org/sax/properties/lexical-handler",
998
                         chandler);
999
      parser.setContentHandler((ContentHandler)chandler);
1000
      parser.setEntityResolver((EntityResolver)dbresolver);
1001
      parser.setDTDHandler((DTDHandler)dtdhandler);
1002
      parser.setErrorHandler((ErrorHandler)chandler);
1003

    
1004
    } catch (Exception e) {
1005
      throw e;
1006
    }
1007

    
1008
    return parser;
1009
  }
1010

    
1011
  /** Save a document entry in the xml_revisions table */
1012
  private static void archiveDocRevision(Connection conn, String docid,
1013
                                         String user) 
1014
                                         throws SQLException {
1015
    // create a record in xml_revisions table 
1016
    // for that document as selected from xml_documents
1017
    PreparedStatement pstmt = conn.prepareStatement(
1018
      "INSERT INTO xml_revisions " +
1019
        "(revisionid, docid, rootnodeid, docname, doctype, doctitle, " +
1020
        "user_owner, user_updated, date_created, date_updated, server_location, " +
1021
        "rev)" +
1022
      "SELECT null, ?, rootnodeid, docname, doctype, doctitle," + 
1023
        "user_owner, ?, sysdate, sysdate, server_location, rev "+
1024
      "FROM xml_documents " +
1025
      "WHERE docid = ?");
1026
    // Bind the values to the query and execute it
1027
    pstmt.setString(1, docid);
1028
    pstmt.setString(2, user);
1029
    pstmt.setString(3, docid);
1030
    pstmt.execute();
1031
    pstmt.close();
1032

    
1033
  }
1034

    
1035
  /**
1036
   * the main routine used to test the DBWriter utility.
1037
   * <p>
1038
   * Usage: java DocumentImpl <-f filename -a action -d docid>
1039
   *
1040
   * @param filename the filename to be loaded into the database
1041
   * @param action the action to perform (READ, INSERT, UPDATE, DELETE)
1042
   * @param docid the id of the document to process
1043
   */
1044
  static public void main(String[] args) {
1045
     
1046
    try {
1047
      String filename    = null;
1048
      String aclfilename = null;
1049
      String action      = null;
1050
      String docid       = null;
1051
      boolean showRuntime = false;
1052
      boolean useOldReadAlgorithm = false;
1053

    
1054
      // Parse the command line arguments
1055
      for ( int i=0 ; i < args.length; ++i ) {
1056
        if ( args[i].equals( "-f" ) ) {
1057
          filename =  args[++i];
1058
        } else if ( args[i].equals( "-c" ) ) {
1059
          aclfilename =  args[++i];
1060
        } else if ( args[i].equals( "-a" ) ) {
1061
          action =  args[++i];
1062
        } else if ( args[i].equals( "-d" ) ) {
1063
          docid =  args[++i];
1064
        } else if ( args[i].equals( "-t" ) ) {
1065
          showRuntime = true;
1066
        } else if ( args[i].equals( "-old" ) ) {
1067
          useOldReadAlgorithm = true;
1068
        } else {
1069
          System.err.println
1070
            ( "   args[" +i+ "] '" +args[i]+ "' ignored." );
1071
        }
1072
      }
1073
      
1074
      // Check if the required arguments are provided
1075
      boolean argsAreValid = false;
1076
      if (action != null) {
1077
        if (action.equals("INSERT")) {
1078
          if (filename != null) {
1079
            argsAreValid = true;
1080
          } 
1081
        } else if (action.equals("UPDATE")) {
1082
          if ((filename != null) && (docid != null)) {
1083
            argsAreValid = true;
1084
          } 
1085
        } else if (action.equals("DELETE")) {
1086
          if (docid != null) {
1087
            argsAreValid = true;
1088
          } 
1089
        } else if (action.equals("READ")) {
1090
          if (docid != null) {
1091
            argsAreValid = true;
1092
          } 
1093
        } 
1094
      } 
1095

    
1096
      // Print usage message if the arguments are not valid
1097
      if (!argsAreValid) {
1098
        System.err.println("Wrong number of arguments!!!");
1099
        System.err.println(
1100
          "USAGE: java DocumentImpl [-t] <-a INSERT> [-d docid] <-f filename> [-c aclfilename]");
1101
        System.err.println(
1102
          "   OR: java DocumentImpl [-t] <-a UPDATE -d docid -f filename> [-c aclfilename]");
1103
        System.err.println(
1104
          "   OR: java DocumentImpl [-t] <-a DELETE -d docid>");
1105
        System.err.println(
1106
          "   OR: java DocumentImpl [-t] [-old] <-a READ -d docid>");
1107
        return;
1108
      }
1109
      
1110
      // Time the request if asked for
1111
      double startTime = System.currentTimeMillis();
1112
      
1113
      // Open a connection to the database
1114
      MetaCatUtil util = new MetaCatUtil();
1115
      Connection dbconn = util.openDBConnection();
1116

    
1117
      double connTime = System.currentTimeMillis();
1118
      // Execute the action requested (READ, INSERT, UPDATE, DELETE)
1119
      if (action.equals("READ")) {
1120
          DocumentImpl xmldoc = new DocumentImpl( dbconn, docid );
1121
          if (useOldReadAlgorithm) {
1122
            System.out.println(xmldoc.readUsingSlowAlgorithm());
1123
          } else {
1124
            xmldoc.toXml(new PrintWriter(System.out));
1125
          }
1126
      } else if (action.equals("DELETE")) {
1127
        DocumentImpl.delete(dbconn, docid, null, null);
1128
        System.out.println("Document deleted: " + docid);
1129
      } else {
1130
        String newdocid = DocumentImpl.write(dbconn, filename, aclfilename,
1131
                                             action, docid, null, null);
1132
        if ((docid != null) && (!docid.equals(newdocid))) {
1133
          if (action.equals("INSERT")) {
1134
            System.out.println("New document ID generated!!! ");
1135
          } else if (action.equals("UPDATE")) {
1136
            System.out.println("ERROR: Couldn't update document!!! ");
1137
          }
1138
        } else if ((docid == null) && (action.equals("UPDATE"))) {
1139
          System.out.println("ERROR: Couldn't update document!!! ");
1140
        }
1141
        System.out.println("Document processing finished for: " + filename
1142
              + " (" + newdocid + ")");
1143
      }
1144

    
1145
      double stopTime = System.currentTimeMillis();
1146
      double dbOpenTime = (connTime - startTime)/1000;
1147
      double insertTime = (stopTime - connTime)/1000;
1148
      double executionTime = (stopTime - startTime)/1000;
1149
      if (showRuntime) {
1150
        System.out.println("\n\nTotal Execution time was: " + 
1151
                           executionTime + " seconds.");
1152
        System.out.println("Time to open DB connection was: " + dbOpenTime + 
1153
                           " seconds.");
1154
        System.out.println("Time to insert document was: " + insertTime +
1155
                           " seconds.");
1156
      }
1157
      dbconn.close();
1158
    } catch (McdbException me) {
1159
      me.toXml(new PrintWriter(System.err));
1160
    } catch (AccessionNumberException ane) {
1161
      System.out.println("ERROR: Couldn't delete document!!! ");
1162
      System.out.println(ane.getMessage());
1163
    } catch (Exception e) {
1164
      System.err.println("EXCEPTION HANDLING REQUIRED");
1165
      System.err.println(e.getMessage());
1166
      e.printStackTrace(System.err);
1167
    }
1168
  }
1169
}
(21-21/36)