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: bojilova $'
10
 *     '$Date: 2001-01-04 17:12:29 -0800 (Thu, 04 Jan 2001) $'
11
 * '$Revision: 638 $'
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
      DocumentIdentifier id = new DocumentIdentifier(docid);
86
      
87
  
88
      // Look up the document information
89
      getDocumentInfo(docid);
90
      
91
      // Download all of the document nodes using a single SQL query
92
      // The sort order of the records is determined by the NodeComparator
93
      // class, and needs to represent a depth-first traversal for the
94
      // toXml() method to work properly
95
      nodeRecordList = getNodeRecordList(rootnodeid);
96
  
97
    } catch (McdbException ex) {
98
      throw ex;
99
    } catch (Throwable t) {
100
      throw new McdbException("Error reading document " + docid + ".");
101
    }
102
  }
103

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

    
147
  /**
148
   * get the document name
149
   */
150
  public String getDocname() {
151
    return docname;
152
  }
153

    
154
  /**
155
   * get the document type (which is the PublicID)
156
   */
157
  public String getDoctype() {
158
    return doctype;
159
  }
160

    
161
  /**
162
   * get the system identifier
163
   */
164
  public String getSystemID() {
165
    return system_id;
166
  }
167

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

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

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

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

    
248
    // Create the elements from the downloaded data in the TreeSet
249
    rootNode = new ElementNode(nodeRecordList, rootnodeid);
250

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

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

    
281
    MetaCatUtil util = new MetaCatUtil();
282
    
283
    Stack openElements = new Stack();
284
    boolean atRootElement = true;
285
    boolean previousNodeWasElement = false;
286

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

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

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

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

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

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

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

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

    
390
    // Print the final end tag for the root element
391
    NodeRecord currentElement = (NodeRecord)openElements.pop();
392
    util.debugMessage("\n POPPED: " + currentElement.nodename);
393
    out.print("</" + currentElement.nodename + ">" );
394
    out.flush();
395
  }
396
  
397
  private boolean isRevisionOnly(DocumentIdentifier docid) throws Exception
398
  {
399
    //System.out.println("inRevisionOnly");
400
    PreparedStatement pstmt;
401
    String rev = docid.getRev();
402
    String newid = docid.getIdentifier();
403
    pstmt = conn.prepareStatement("select rev from xml_documents " +
404
                                  "where docid like '" + newid + "'");
405
    pstmt.execute();
406
    ResultSet rs = pstmt.getResultSet();
407
    boolean tablehasrows = rs.next();
408
    if(rev.equals("newest") || rev.equals("all"))
409
    {
410
      return false;
411
    }
412
    
413
    if(tablehasrows)
414
    {
415
      int r = rs.getInt(1);
416
      if(new Integer(rev).intValue() == r)
417
      { //the current revision in in xml_documents
418
        //System.out.println("returning false");
419
        return false;
420
      }
421
      else if(new Integer(rev).intValue() < r)
422
      { //the current revision is in xml_revisions.
423
        //System.out.println("returning true");
424
        return true;
425
      }
426
      else if(new Integer(rev).intValue() > r)
427
      { //error, rev cannot be greater than r
428
        throw new Exception("requested revision cannot be greater than " +
429
                            "the latest revision number.");
430
      }
431
    }
432
    throw new Exception("the requested docid '" + docid.toString() + 
433
                        "' does not exist");
434
  }
435

    
436
  private void getDocumentInfo(String docid) throws McdbException, 
437
                                                    AccessionNumberException
438
  {
439
    getDocumentInfo(new DocumentIdentifier(docid));
440
  }
441
  
442
  /**
443
   * Look up the document type information from the database
444
   *
445
   * @param docid the id of the document to look up
446
   */
447
  private void getDocumentInfo(DocumentIdentifier docid) throws McdbException 
448
  {
449
    PreparedStatement pstmt;
450
    String table = "xml_documents";
451
    
452
    try
453
    {
454
      if(isRevisionOnly(docid))
455
      { //pull the document from xml_revisions instead of from xml_documents;
456
        table = "xml_revisions";
457
      }
458
    }
459
    catch(Exception e)
460
    {
461
      System.out.println("error in getDocumentInfo: " + e.getMessage());
462
    }
463
    
464
    //deal with the key words here.
465
    
466
    if(docid.getRev().equals("all"))
467
    {
468
      
469
    }
470
    
471
    try {
472
      StringBuffer sql = new StringBuffer();
473
      sql.append("SELECT docname, doctype, rootnodeid,doctitle, ");
474
      sql.append("date_created, date_updated, ");
475
      sql.append("user_owner, user_updated, server_location, ");
476
      sql.append("rev FROM ").append(table);
477
      sql.append(" WHERE docid LIKE '").append(docid.getIdentifier());
478
      sql.append("' and rev like '").append(docid.getRev()).append("'");
479
      //System.out.println(sql.toString());
480
      pstmt =
481
        conn.prepareStatement(sql.toString());
482
      // Bind the values to the query
483
      //pstmt.setString(1, docid.getIdentifier());
484
      //pstmt.setString(2, docid.getRev());
485

    
486
      pstmt.execute();
487
      ResultSet rs = pstmt.getResultSet();
488
      boolean tableHasRows = rs.next();
489
      if (tableHasRows) {
490
        this.docname        = rs.getString(1);
491
        this.doctype        = rs.getString(2);
492
        this.rootnodeid     = rs.getLong(3);
493
        this.doctitle       = rs.getString(4);
494
        this.createdate     = rs.getString(5);
495
        this.updatedate     = rs.getString(6);
496
        this.userowner      = rs.getString(7);
497
        this.userupdated    = rs.getString(8);
498
        this.serverlocation = rs.getInt(9);
499
        //this.publicaccess   = rs.getInt(10);
500
        this.rev            = rs.getInt(10);
501
      } 
502
      pstmt.close();
503

    
504
      if (this.doctype != null) {
505
        pstmt =
506
          conn.prepareStatement("SELECT system_id " +
507
                                  "FROM xml_catalog " +
508
                                 "WHERE public_id LIKE ?");
509
        // Bind the values to the query
510
        pstmt.setString(1, doctype);
511
  
512
        pstmt.execute();
513
        rs = pstmt.getResultSet();
514
        tableHasRows = rs.next();
515
        if (tableHasRows) {
516
          this.system_id  = rs.getString(1);
517
        } 
518
        pstmt.close();
519
      }
520
    } catch (SQLException e) {
521
      System.out.println("error in getDocumentInfo: " + e.getMessage());
522
      e.printStackTrace(System.out);
523
      throw new McdbException("Error accessing database connection.", e);
524
    }
525

    
526
    if (this.docname == null) {
527
      throw new McdbDocNotFoundException("Document not found: " + docid);
528
    }
529
  }
530

    
531
  /**
532
   * Look up the node data from the database
533
   *
534
   * @param rootnodeid the id of the root node of the node tree to look up
535
   */
536
  private TreeSet getNodeRecordList(long rootnodeid) throws McdbException 
537
  {
538
    PreparedStatement pstmt;
539
    TreeSet nodeRecordList = new TreeSet(new NodeComparator());
540
    long nodeid = 0;
541
    long parentnodeid = 0;
542
    long nodeindex = 0;
543
    String nodetype = null;
544
    String nodename = null;
545
    String nodedata = null;
546

    
547
    try {
548
      pstmt =
549
      conn.prepareStatement("SELECT nodeid,parentnodeid,nodeindex, " +
550
           "nodetype,nodename,"+               
551
           "replace(" +
552
           "replace(" +
553
           "replace(nodedata,'&','&amp;') " +
554
           ",'<','&lt;') " +
555
           ",'>','&gt;') " +
556
           "FROM xml_nodes WHERE rootnodeid = ?");
557

    
558
      // Bind the values to the query
559
      pstmt.setLong(1, rootnodeid);
560

    
561
      pstmt.execute();
562
      ResultSet rs = pstmt.getResultSet();
563
      boolean tableHasRows = rs.next();
564
      while (tableHasRows) {
565
        nodeid = rs.getLong(1);
566
        parentnodeid = rs.getLong(2);
567
        nodeindex = rs.getLong(3);
568
        nodetype = rs.getString(4);
569
        nodename = rs.getString(5);
570
        nodedata = rs.getString(6);
571

    
572
        // add the data to the node record list hashtable
573
        NodeRecord currentRecord = new NodeRecord(nodeid, parentnodeid, 
574
                                   nodeindex, nodetype, nodename, nodedata);
575
        nodeRecordList.add(currentRecord);
576

    
577
        // Advance to the next node
578
        tableHasRows = rs.next();
579
      } 
580
      pstmt.close();
581

    
582
    } catch (SQLException e) {
583
      throw new McdbException("Error accessing database connection.", e);
584
    }
585

    
586
    if (nodeRecordList != null) {
587
      return nodeRecordList;
588
    } else {
589
      throw new McdbException("Error getting node data: " + docid);
590
    }
591
  }
592
  
593
  /** creates SQL code and inserts new document into DB connection 
594
   default serverCode of 1*/
595
  private void writeDocumentToDB(String action, String user)
596
               throws SQLException, Exception
597
  {
598
    writeDocumentToDB(action, user, 1);
599
  }
600

    
601
 /** creates SQL code and inserts new document into DB connection */
602
  private void writeDocumentToDB(String action, String user, int serverCode) 
603
               throws SQLException, Exception {
604
    try {
605
      PreparedStatement pstmt = null;
606

    
607
      if (action.equals("INSERT")) {
608
        //AccessionNumber ac = new AccessionNumber();
609
        //this.docid = ac.generate(docid, "INSERT");
610
        pstmt = conn.prepareStatement(
611
            "INSERT INTO xml_documents " +
612
            "(docid, rootnodeid, docname, doctype, user_owner, " +
613
            "user_updated, date_created, date_updated, server_location) " +
614
            "VALUES (?, ?, ?, ?, ?, ?, sysdate, sysdate, ?)");
615
        //note that the server_location is set to 1. 
616
        //this means that "localhost" in the xml_replication table must
617
        //always be the first entry!!!!!
618
        
619
        // Bind the values to the query
620
        pstmt.setString(1, this.docid);
621
        pstmt.setLong(2, rootnodeid);
622
        pstmt.setString(3, docname);
623
        pstmt.setString(4, doctype);
624
        pstmt.setString(5, user);
625
        pstmt.setString(6, user);
626
        pstmt.setInt(7, serverCode);
627
      } else if (action.equals("UPDATE")) {
628

    
629
        // Save the old document entry in a backup table
630
        DocumentImpl.archiveDocRevision( conn, docid, user );
631
        DocumentImpl thisdoc = new DocumentImpl(conn, docid);
632
        int thisrev = thisdoc.getRev();
633
        thisrev++;
634
        // Delete index for the old version of docid
635
        // The new index is inserting on the next calls to DBSAXNode
636
        pstmt = conn.prepareStatement(
637
                "DELETE FROM xml_index WHERE docid='" + this.docid + "'");
638
        pstmt.execute();
639
        pstmt.close();
640

    
641
        // Update the new document to reflect the new node tree
642
        pstmt = conn.prepareStatement(
643
            "UPDATE xml_documents " +
644
            "SET rootnodeid = ?, docname = ?, doctype = ?, " +
645
            "user_updated = ?, date_updated = sysdate, " +
646
            "server_location = ?, rev = ? WHERE docid LIKE ?");
647
        // Bind the values to the query
648
        pstmt.setLong(1, rootnodeid);
649
        pstmt.setString(2, docname);
650
        pstmt.setString(3, doctype);
651
        pstmt.setString(4, user);
652
        pstmt.setInt(5, serverCode);
653
        pstmt.setInt(6, thisrev);
654
        pstmt.setString(7, this.docid);
655

    
656
      } else {
657
        System.err.println("Action not supported: " + action);
658
      }
659

    
660
      // Do the insertion
661
      pstmt.execute();
662
      pstmt.close();
663

    
664
    } catch (SQLException sqle) {
665
      throw sqle;
666
    } catch (Exception e) {
667
      throw e;
668
    }
669
  }
670

    
671
  /**
672
   * Get the document title
673
   */
674
  public String getTitle() {
675
    return doctitle;
676
  }
677

    
678
  /**
679
   * Set the document title
680
   *
681
   * @param title the new title for the document
682
   */
683

    
684
  public void setTitle( String title ) {
685
    this.doctitle = title;
686
    try {
687
      PreparedStatement pstmt;
688
      pstmt = conn.prepareStatement(
689
            "UPDATE xml_documents " +
690
            " SET doctitle = ? " +
691
            "WHERE docid = ?");
692

    
693
      // Bind the values to the query
694
      pstmt.setString(1, doctitle);
695
      pstmt.setString(2, docid);
696

    
697
      // Do the insertion
698
      pstmt.execute();
699
      pstmt.close();
700
    } catch (SQLException e) {
701
      System.out.println(e.getMessage());
702
    }
703
  }
704

    
705
  /**
706
   * Write an XML file to the database, given a filename
707
   *
708
   * @param conn the JDBC connection to the database
709
   * @param filename the filename to be loaded into the database
710
   * @param action the action to be performed (INSERT OR UPDATE)
711
   * @param docid the docid to use for the INSERT OR UPDATE
712
   */
713
  public static String write(Connection conn,String filename,
714
                             String aclfilename,String dtdfilename,
715
                             String action, String docid, String user,
716
                             String group )
717
                throws Exception {
718
                  
719
    Reader acl = null;
720
    if ( aclfilename != null ) {
721
      acl = new FileReader(new File(aclfilename).toString());
722
    }
723
    Reader dtd = null;
724
    if ( dtdfilename != null ) {
725
      dtd = new FileReader(new File(dtdfilename).toString());
726
    }
727
    return write ( conn, new FileReader(new File(filename).toString()),
728
                   acl, dtd, action, docid, user, group);
729
  }
730

    
731
  public static String write(Connection conn,Reader xml,Reader acl,Reader dtd,
732
                             String action, String docid, String user,
733
                             String group )
734
                throws Exception {
735
    return write ( conn, xml, acl, dtd, action, docid, user, group, 1, false);
736
  }
737

    
738
  public static String write(Connection conn,Reader xml,Reader acl,
739
                             String action, String docid, String user,
740
                             String group )
741
                throws Exception {
742
    if(action.equals("UPDATE"))
743
    {//if the document is being updated then use the servercode from the 
744
     //originally inserted document.
745
      DocumentImpl doc = new DocumentImpl(conn, docid);
746
      int servercode = doc.getServerlocation();
747
      return write(conn,xml,acl,action,docid,user,group,servercode);
748
    }
749
    else
750
    {//if the file is being inserted then the servercode is always 1
751
      return write(conn, xml, acl, action, docid, user, group, 1);
752
    }
753
  }
754
  
755
  public static String write( Connection conn, Reader xml,
756
                              String action, String docid, String user,
757
                              String group, int serverCode )
758
                throws Exception
759
  {
760
    return write(conn,xml,null,action,docid,user,group,serverCode);
761
  }
762
  
763
  public static String write( Connection conn,Reader xml,Reader acl,
764
                              String action, String docid, String user,
765
                              String group, int serverCode) 
766
                throws Exception
767
  {
768
    return write(conn,xml,acl,null,action,docid,user,group,serverCode,false);
769
  }
770
  
771
  public static String write( Connection conn,Reader xml,Reader acl,
772
                              String action, String docid, String user,
773
                              String group, int serverCode, boolean override)
774
                throws Exception
775
  {
776
    return write(conn,xml,acl,null,action,docid,user,group,serverCode,override);
777
  }
778
  
779
  /**
780
   * Write an XML file to the database, given a Reader
781
   *
782
   * @param conn the JDBC connection to the database
783
   * @param xml the xml stream to be loaded into the database
784
   * @param action the action to be performed (INSERT OR UPDATE)
785
   * @param docid the docid to use for the INSERT OR UPDATE
786
   * @param user the user that owns the document
787
   * @param group the group to which user belongs
788
   * @param serverCode the serverid from xml_replication on which this document
789
   *        resides.
790
   * @param override flag to stop insert replication checking.
791
   *        if override = true then a document not belonging to the local server
792
   *        will not be checked upon update for a file lock.
793
   *        if override = false then a document not from this server, upon 
794
   *        update will be locked and version checked.
795
   */
796

    
797
  public static String write( Connection conn,Reader xml,Reader acl,Reader dtd,
798
                              String action, String docid, String user,
799
                              String group, int serverCode, boolean override)
800
                throws Exception
801
  {
802
    if(action.equals("UPDATE"))
803
    {
804
      RelationHandler.deleteRelations(docid);
805
    }
806
    
807
    int updaterev;
808
    MetaCatUtil util = new MetaCatUtil();
809
        // Determine if the docid is OK for INSERT or UPDATE
810
    AccessionNumber ac = new AccessionNumber(conn);
811
    String newdocid = ac.generate(docid, action);
812
    
813
    MetaCatUtil.debugMessage("action: " + action + " servercode: " + 
814
                             serverCode + " override: " + override);
815
                        
816
    if((serverCode != 1 && action.equals("UPDATE")) && !override)
817
    { //if this document being written is not a resident of this server then
818
      //we need to try to get a lock from it's resident server.  If the
819
      //resident server will not give a lock then we send the user a message
820
      //saying that he/she needs to download a new copy of the file and
821
      //merge the differences manually.
822
      int istreamInt; 
823
      char istreamChar;
824
      DocumentImpl newdoc = new DocumentImpl(conn, docid);
825
      updaterev = newdoc.getRev();
826
      String server = MetacatReplication.getServer(serverCode);
827
      MetacatReplication.replLog("attempting to lock " + docid);
828
      URL u = new URL("http://" + server + "?action=getlock&updaterev=" + 
829
                      updaterev + "&docid=" + docid);
830
      System.out.println("sending message: " + u.toString());
831
      String serverResStr = MetacatReplication.getURLContent(u);
832
      String openingtag = serverResStr.substring(0, serverResStr.indexOf(">")+1);
833
      
834
      if(openingtag.equals("<lockgranted>"))
835
      {//the lock was granted go ahead with the insert
836
        try 
837
        {
838
          MetacatReplication.replLog("lock granted for " + docid + " from " +
839
                                      server);
840
          XMLReader parser = initializeParser(conn, action, newdocid, user,
841
                                              serverCode, dtd);
842
          conn.setAutoCommit(false);
843
          parser.parse(new InputSource(xml));
844
          conn.commit();
845
          
846
          // if acltext is provided for @xml, store acl info into db
847
          if ( acl != null ) 
848
          {
849
            AccessControlList aclobj=new AccessControlList(conn,null,newdocid,acl);
850
            conn.commit();
851
          } 
852

    
853
          conn.setAutoCommit(true);
854
        } 
855
        catch (Exception e) 
856
        {
857
          conn.rollback();
858
          conn.setAutoCommit(true);
859
          throw e;
860
        }
861
                
862
        //after inserting the document locally, tell the document's home server
863
        //to come get a copy from here.
864
        ForceReplicationHandler frh = new ForceReplicationHandler(docid);
865
        
866
        if ( (docid != null) && !(newdocid.equals(docid)) ) 
867
        {
868
          return new String("New document ID generated:" + newdocid);
869
        } 
870
        else 
871
        {
872
          return newdocid;
873
        }
874
      }
875
      else if(openingtag.equals("<filelocked>"))
876
      {//the file is currently locked by another user
877
       //notify our user to wait a few minutes, check out a new copy and try
878
       //again.
879
        //System.out.println("file locked");
880
        MetacatReplication.replLog("lock denied for " + docid + " on " +
881
                                   server + " reason: file already locked");
882
        throw new Exception("The file specified is already locked by another " +
883
                            "user.  Please wait 30 seconds, checkout the " +
884
                            "newer document, merge your changes and try " +
885
                            "again.");
886
      }
887
      else if(openingtag.equals("<outdatedfile>"))
888
      {//our file is outdated.  notify our user to check out a new copy of the
889
       //file and merge his version with the new version.
890
        //System.out.println("outdated file");
891
        MetacatReplication.replLog("lock denied for " + docid + " on " +
892
                                    server + " reason: local file outdated");
893
        throw new Exception("The file you are trying to update is an outdated" +
894
                            " version.  Please checkout the newest document, " +
895
                            "merge your changes and try again.");
896
      }
897
    }
898
    
899
    if ( action.equals("UPDATE") ) {
900
      // check for 'write' permission for 'user' to update this document
901

    
902
      if ( !hasPermission(conn, user, group, docid) ) {
903
        throw new Exception("User " + user + 
904
              " does not have permission to update XML Document #" + docid);
905
      }          
906
    }
907

    
908
    try 
909
    { 
910
      XMLReader parser=initializeParser(conn,action,newdocid,user,serverCode,dtd);
911
      conn.setAutoCommit(false);
912
      parser.parse(new InputSource(xml));
913
      conn.commit();
914

    
915
      // if acltext is provided for @xml, store acl info into db
916
      if ( acl != null ) 
917
      {
918
        AccessControlList aclobj=new AccessControlList(conn,null,newdocid,acl);
919
        conn.commit();
920
      } 
921

    
922
      conn.setAutoCommit(true);
923

    
924
    } 
925
    catch (Exception e) 
926
    {
927
      conn.rollback();
928
      conn.setAutoCommit(true);
929
      throw e;
930
    }
931
    
932
    //force replicate out the new document to each server in our server list.
933
    if(serverCode == 1)
934
    { //start the thread to replicate this new document out to the other servers
935
      ForceReplicationHandler frh = new ForceReplicationHandler(newdocid, 
936
                                                                action);
937
    }
938
      
939
    if ( (docid != null) && !(newdocid.equals(docid)) ) 
940
    {
941
      return new String("New document ID generated:" + newdocid);
942
    } 
943
    else 
944
    {
945
      return newdocid;
946
    }
947
  }
948

    
949
  /**
950
   * Delete an XML file from the database (actually, just make it a revision
951
   * in the xml_revisions table)
952
   *
953
   * @param docid the ID of the document to be deleted from the database
954
   */
955
  public static void delete( Connection conn, String docid,
956
                                 String user, String group )
957
                throws Exception {
958
    DocumentIdentifier id = new DocumentIdentifier(docid);
959
    docid = id.getSiteCode() + id.getSeparator() + id.getUniqueId();
960
    
961
    // Determine if the docid is OK for DELETE
962
    AccessionNumber ac = new AccessionNumber(conn);
963
    String newdocid = ac.generate(docid, "DELETE");
964

    
965
    // check for 'write' permission for 'user' to delete this document
966
    if ( !hasPermission(conn, user, group, docid) ) {
967
      throw new Exception("User " + user + 
968
              " does not have permission to delete XML Document #" + docid);
969
    }
970

    
971
    conn.setAutoCommit(false);
972
    // Copy the record to the xml_revisions table
973
    DocumentImpl.archiveDocRevision( conn, docid, user );
974

    
975
    // Now delete it from the xml_documents table
976
    
977
    Statement stmt = conn.createStatement();
978
    stmt.execute("DELETE FROM xml_index WHERE docid = '" + docid + "'");
979
    stmt.execute("DELETE FROM xml_documents WHERE docid = '" + docid + "'");
980
    stmt.close();
981
    conn.commit();
982
    conn.setAutoCommit(true);
983
    //IF this is a package document:
984
    //delete all of the relations that this document created.
985
    //if the deleted document is a package document its relations should 
986
    //no longer be active if it has been deleted from the system.
987
    RelationHandler.deleteRelations(docid);
988
  }
989

    
990
  /** 
991
    * Check for "WRITE" permission on @docid for @user and/or @group 
992
    * from DB connection 
993
    */
994
  private static boolean hasPermission( Connection conn, String user,
995
                                        String group, String docid) 
996
                         throws SQLException 
997
  {
998
    // b' of the command line invocation
999
    if ( (user == null) && (group == null) ) {
1000
      return true;
1001
    }
1002

    
1003
    // Check for WRITE permission on @docid for @user and/or @group
1004
    AccessControlList aclobj = new AccessControlList(conn);
1005
    boolean hasPermission = aclobj.hasPermission("WRITE",user,docid);
1006
    if ( !hasPermission && group != null ) {
1007
      hasPermission = aclobj.hasPermission("WRITE",group,docid);
1008
    }
1009
    
1010
    return hasPermission;
1011
  }
1012

    
1013
  /**
1014
   * Set up the parser handlers for writing the document to the database
1015
   */
1016
  private static XMLReader initializeParser(Connection conn, String action,
1017
                                            String docid, String user,
1018
                                            int serverCode, Reader dtd) 
1019
                           throws Exception 
1020
  {
1021
    XMLReader parser = null;
1022
    //
1023
    // Set up the SAX document handlers for parsing
1024
    //
1025
    try {
1026
      ContentHandler chandler = new DBSAXHandler(conn,action,docid,user,serverCode);
1027
      EntityResolver eresolver= new DBEntityResolver(conn,(DBSAXHandler)chandler,dtd);
1028
      DTDHandler dtdhandler   = new DBDTDHandler(conn);
1029

    
1030
      // Get an instance of the parser
1031
      MetaCatUtil util = new MetaCatUtil();
1032
      String parserName = util.getOption("saxparser");
1033
      parser = XMLReaderFactory.createXMLReader(parserName);
1034

    
1035
      // Turn off validation
1036
      parser.setFeature("http://xml.org/sax/features/validation", false);
1037
      
1038
      // Set Handlers in the parser
1039
      parser.setProperty("http://xml.org/sax/properties/declaration-handler",
1040
                         chandler);
1041
      parser.setProperty("http://xml.org/sax/properties/lexical-handler",
1042
                         chandler);
1043
      parser.setContentHandler((ContentHandler)chandler);
1044
      parser.setEntityResolver((EntityResolver)eresolver);
1045
      parser.setDTDHandler((DTDHandler)dtdhandler);
1046
      parser.setErrorHandler((ErrorHandler)chandler);
1047

    
1048
    } catch (Exception e) {
1049
      throw e;
1050
    }
1051

    
1052
    return parser;
1053
  }
1054

    
1055
  /** Save a document entry in the xml_revisions table */
1056
  private static void archiveDocRevision(Connection conn, String docid,
1057
                                         String user) 
1058
                                         throws SQLException {
1059
    // create a record in xml_revisions table 
1060
    // for that document as selected from xml_documents
1061
    PreparedStatement pstmt = conn.prepareStatement(
1062
      "INSERT INTO xml_revisions " +
1063
        "(revisionid, docid, rootnodeid, docname, doctype, doctitle, " +
1064
        "user_owner, user_updated, date_created, date_updated, server_location, " +
1065
        "rev)" +
1066
      "SELECT null, ?, rootnodeid, docname, doctype, doctitle," + 
1067
        "user_owner, ?, sysdate, sysdate, server_location, rev "+
1068
      "FROM xml_documents " +
1069
      "WHERE docid = ?");
1070
    // Bind the values to the query and execute it
1071
    pstmt.setString(1, docid);
1072
    pstmt.setString(2, user);
1073
    pstmt.setString(3, docid);
1074
    pstmt.execute();
1075
    pstmt.close();
1076

    
1077
  }
1078

    
1079
  /**
1080
   * the main routine used to test the DBWriter utility.
1081
   * <p>
1082
   * Usage: java DocumentImpl <-f filename -a action -d docid>
1083
   *
1084
   * @param filename the filename to be loaded into the database
1085
   * @param action the action to perform (READ, INSERT, UPDATE, DELETE)
1086
   * @param docid the id of the document to process
1087
   */
1088
  static public void main(String[] args) {
1089
     
1090
    try {
1091
      String filename    = null;
1092
      String aclfilename = null;
1093
      String dtdfilename = null;
1094
      String action      = null;
1095
      String docid       = null;
1096
      boolean showRuntime = false;
1097
      boolean useOldReadAlgorithm = false;
1098

    
1099
      // Parse the command line arguments
1100
      for ( int i=0 ; i < args.length; ++i ) {
1101
        if ( args[i].equals( "-f" ) ) {
1102
          filename =  args[++i];
1103
        } else if ( args[i].equals( "-c" ) ) {
1104
          aclfilename =  args[++i];
1105
        } else if ( args[i].equals( "-r" ) ) {
1106
          dtdfilename =  args[++i];
1107
        } else if ( args[i].equals( "-a" ) ) {
1108
          action =  args[++i];
1109
        } else if ( args[i].equals( "-d" ) ) {
1110
          docid =  args[++i];
1111
        } else if ( args[i].equals( "-t" ) ) {
1112
          showRuntime = true;
1113
        } else if ( args[i].equals( "-old" ) ) {
1114
          useOldReadAlgorithm = true;
1115
        } else {
1116
          System.err.println
1117
            ( "   args[" +i+ "] '" +args[i]+ "' ignored." );
1118
        }
1119
      }
1120
      
1121
      // Check if the required arguments are provided
1122
      boolean argsAreValid = false;
1123
      if (action != null) {
1124
        if (action.equals("INSERT")) {
1125
          if (filename != null) {
1126
            argsAreValid = true;
1127
          } 
1128
        } else if (action.equals("UPDATE")) {
1129
          if ((filename != null) && (docid != null)) {
1130
            argsAreValid = true;
1131
          } 
1132
        } else if (action.equals("DELETE")) {
1133
          if (docid != null) {
1134
            argsAreValid = true;
1135
          } 
1136
        } else if (action.equals("READ")) {
1137
          if (docid != null) {
1138
            argsAreValid = true;
1139
          } 
1140
        } 
1141
      } 
1142

    
1143
      // Print usage message if the arguments are not valid
1144
      if (!argsAreValid) {
1145
        System.err.println("Wrong number of arguments!!!");
1146
        System.err.println(
1147
          "USAGE: java DocumentImpl [-t] <-a INSERT> [-d docid] <-f filename> "+
1148
          "[-c aclfilename] [-r dtdfilename]");
1149
        System.err.println(
1150
          "   OR: java DocumentImpl [-t] <-a UPDATE -d docid -f filename> " +
1151
          "[-c aclfilename] [-r dtdfilename]");
1152
        System.err.println(
1153
          "   OR: java DocumentImpl [-t] <-a DELETE -d docid>");
1154
        System.err.println(
1155
          "   OR: java DocumentImpl [-t] [-old] <-a READ -d docid>");
1156
        return;
1157
      }
1158
      
1159
      // Time the request if asked for
1160
      double startTime = System.currentTimeMillis();
1161
      
1162
      // Open a connection to the database
1163
      MetaCatUtil util = new MetaCatUtil();
1164
      Connection dbconn = util.openDBConnection();
1165

    
1166
      double connTime = System.currentTimeMillis();
1167
      // Execute the action requested (READ, INSERT, UPDATE, DELETE)
1168
      if (action.equals("READ")) {
1169
          DocumentImpl xmldoc = new DocumentImpl( dbconn, docid );
1170
          if (useOldReadAlgorithm) {
1171
            System.out.println(xmldoc.readUsingSlowAlgorithm());
1172
          } else {
1173
            xmldoc.toXml(new PrintWriter(System.out));
1174
          }
1175
      } else if (action.equals("DELETE")) {
1176
        DocumentImpl.delete(dbconn, docid, null, null);
1177
        System.out.println("Document deleted: " + docid);
1178
      } else {
1179
        String newdocid = DocumentImpl.write(dbconn, filename, aclfilename,
1180
                                             dtdfilename, action, docid,
1181
                                             null, null);
1182
        if ((docid != null) && (!docid.equals(newdocid))) {
1183
          if (action.equals("INSERT")) {
1184
            System.out.println("New document ID generated!!! ");
1185
          } else if (action.equals("UPDATE")) {
1186
            System.out.println("ERROR: Couldn't update document!!! ");
1187
          }
1188
        } else if ((docid == null) && (action.equals("UPDATE"))) {
1189
          System.out.println("ERROR: Couldn't update document!!! ");
1190
        }
1191
        System.out.println("Document processing finished for: " + filename
1192
              + " (" + newdocid + ")");
1193
      }
1194

    
1195
      double stopTime = System.currentTimeMillis();
1196
      double dbOpenTime = (connTime - startTime)/1000;
1197
      double insertTime = (stopTime - connTime)/1000;
1198
      double executionTime = (stopTime - startTime)/1000;
1199
      if (showRuntime) {
1200
        System.out.println("\n\nTotal Execution time was: " + 
1201
                           executionTime + " seconds.");
1202
        System.out.println("Time to open DB connection was: " + dbOpenTime + 
1203
                           " seconds.");
1204
        System.out.println("Time to insert document was: " + insertTime +
1205
                           " seconds.");
1206
      }
1207
      dbconn.close();
1208
    } catch (McdbException me) {
1209
      me.toXml(new PrintWriter(System.err));
1210
    } catch (AccessionNumberException ane) {
1211
      System.out.println("ERROR: Couldn't delete document!!! ");
1212
      System.out.println(ane.getMessage());
1213
    } catch (Exception e) {
1214
      System.err.println("EXCEPTION HANDLING REQUIRED");
1215
      System.err.println(e.getMessage());
1216
      e.printStackTrace(System.err);
1217
    }
1218
  }
1219
}
(23-23/39)