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: 2000-12-12 13:36:07 -0800 (Tue, 12 Dec 2000) $'
11
 * '$Revision: 600 $'
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 (Exception e) {
593
      throw e;
594
    }
595
  }
596

    
597
  /**
598
   * Get the document title
599
   */
600
  public String getTitle() {
601
    return doctitle;
602
  }
603

    
604
  /**
605
   * Set the document title
606
   *
607
   * @param title the new title for the document
608
   */
609

    
610
  public void setTitle( String title ) {
611
    this.doctitle = title;
612
    try {
613
      PreparedStatement pstmt;
614
      pstmt = conn.prepareStatement(
615
            "UPDATE xml_documents " +
616
            " SET doctitle = ? " +
617
            "WHERE docid = ?");
618

    
619
      // Bind the values to the query
620
      pstmt.setString(1, doctitle);
621
      pstmt.setString(2, docid);
622

    
623
      // Do the insertion
624
      pstmt.execute();
625
      pstmt.close();
626
    } catch (SQLException e) {
627
      System.out.println(e.getMessage());
628
    }
629
  }
630

    
631
  /**
632
   * Write an XML file to the database, given a filename
633
   *
634
   * @param conn the JDBC connection to the database
635
   * @param filename the filename to be loaded into the database
636
   * @param action the action to be performed (INSERT OR UPDATE)
637
   * @param docid the docid to use for the INSERT OR UPDATE
638
   */
639
  public static String write(Connection conn,String filename,
640
                             String aclfilename,String dtdfilename,
641
                             String action, String docid, String user,
642
                             String group )
643
                throws Exception {
644
                  
645
    Reader acl = null;
646
    if ( aclfilename != null ) {
647
      acl = new FileReader(new File(aclfilename).toString());
648
    }
649
    Reader dtd = null;
650
    if ( dtdfilename != null ) {
651
      dtd = new FileReader(new File(dtdfilename).toString());
652
    }
653
    return write ( conn, new FileReader(new File(filename).toString()),
654
                   acl, dtd, action, docid, user, group);
655
  }
656

    
657
  public static String write(Connection conn,Reader xml,Reader acl,Reader dtd,
658
                             String action, String docid, String user,
659
                             String group )
660
                throws Exception {
661
    return write ( conn, xml, acl, dtd, action, docid, user, group, 1, false);
662
  }
663

    
664
  public static String write(Connection conn,Reader xml,Reader acl,
665
                             String action, String docid, String user,
666
                             String group )
667
                throws Exception {
668
    if(action.equals("UPDATE"))
669
    {//if the document is being updated then use the servercode from the 
670
     //originally inserted document.
671
      DocumentImpl doc = new DocumentImpl(conn, docid);
672
      int servercode = doc.getServerlocation();
673
      return write(conn,xml,acl,action,docid,user,group,servercode);
674
    }
675
    else
676
    {//if the file is being inserted then the servercode is always 1
677
      return write(conn, xml, acl, action, docid, user, group, 1);
678
    }
679
  }
680
  
681
  public static String write( Connection conn, Reader xml,
682
                              String action, String docid, String user,
683
                              String group, int serverCode )
684
                throws Exception
685
  {
686
    return write(conn,xml,null,action,docid,user,group,serverCode);
687
  }
688
  
689
  public static String write( Connection conn,Reader xml,Reader acl,
690
                              String action, String docid, String user,
691
                              String group, int serverCode) 
692
                throws Exception
693
  {
694
    return write(conn,xml,acl,null,action,docid,user,group,serverCode,false);
695
  }
696
  
697
  public static String write( Connection conn,Reader xml,Reader acl,
698
                              String action, String docid, String user,
699
                              String group, int serverCode, boolean override)
700
                throws Exception
701
  {
702
    return write(conn,xml,acl,null,action,docid,user,group,serverCode,override);
703
  }
704
  
705
  /**
706
   * Write an XML file to the database, given a Reader
707
   *
708
   * @param conn the JDBC connection to the database
709
   * @param xml the xml stream 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
   * @param user the user that owns the document
713
   * @param group the group to which user belongs
714
   * @param serverCode the serverid from xml_replication on which this document
715
   *        resides.
716
   * @param override flag to stop insert replication checking.
717
   *        if override = true then a document not belonging to the local server
718
   *        will not be checked upon update for a file lock.
719
   *        if override = false then a document not from this server, upon 
720
   *        update will be locked and version checked.
721
   */
722

    
723
  public static String write( Connection conn,Reader xml,Reader acl,Reader dtd,
724
                              String action, String docid, String user,
725
                              String group, int serverCode, boolean override)
726
                throws Exception
727
  {
728
    MetaCatUtil util = new MetaCatUtil();
729
        // Determine if the docid is OK for INSERT or UPDATE
730
    AccessionNumber ac = new AccessionNumber(conn);
731
    String newdocid = ac.generate(docid, action);
732
    
733
    MetaCatUtil.debugMessage("action: " + action + " servercode: " + 
734
                             serverCode + " override: " + override);
735
                        
736
    if((serverCode != 1 && action.equals("UPDATE")) && !override)
737
    { //if this document being written is not a resident of this server then
738
      //we need to try to get a lock from it's resident server.  If the
739
      //resident server will not give a lock then we send the user a message
740
      //saying that he/she needs to download a new copy of the file and
741
      //merge the differences manually.
742
      int istreamInt; 
743
      char istreamChar;
744
      DocumentImpl newdoc = new DocumentImpl(conn, docid);
745
      int updaterev = newdoc.getRev();
746
      String server = MetacatReplication.getServer(serverCode);
747
      MetacatReplication.replLog("attempting to lock " + docid);
748
      URL u = new URL("http://" + server + "?action=getlock&updaterev=" + 
749
                      updaterev + "&docid=" + docid);
750
      System.out.println("sending message: " + u.toString());
751
      String serverResStr = MetacatReplication.getURLContent(u);
752
      String openingtag = serverResStr.substring(0, serverResStr.indexOf(">")+1);
753
      
754
      if(openingtag.equals("<lockgranted>"))
755
      {//the lock was granted go ahead with the insert
756
        try 
757
        {
758
          MetacatReplication.replLog("lock granted for " + docid + " from " +
759
                                      server);
760
          XMLReader parser = initializeParser(conn, action, newdocid, user,
761
                                              serverCode, dtd);
762
          conn.setAutoCommit(false);
763
          parser.parse(new InputSource(xml));
764
          conn.commit();
765
          if ( acl != null ) 
766
          {
767
            if ( action.equals("UPDATE") ) 
768
            {
769
              Statement stmt = conn.createStatement();
770
              stmt.execute("DELETE FROM xml_access WHERE docid='"+newdocid+"'");
771
              stmt.close();
772
            }
773
            AccessControlList aclobj = new AccessControlList(conn,newdocid,acl);
774
            conn.commit();
775
          } 
776
          conn.setAutoCommit(true);
777
        } 
778
        catch (Exception e) 
779
        {
780
          conn.rollback();
781
          conn.setAutoCommit(true);
782
          throw e;
783
        }
784
                
785
        //after inserting the document locally, tell the document's home server
786
        //to come get a copy from here.
787
        ForceReplicationHandler frh = new ForceReplicationHandler(docid);
788
        
789
        if ( (docid != null) && !(newdocid.equals(docid)) ) 
790
        {
791
          return new String("New document ID generated:" + newdocid);
792
        } 
793
        else 
794
        {
795
          return newdocid;
796
        }
797
      }
798
      else if(openingtag.equals("<filelocked>"))
799
      {//the file is currently locked by another user
800
       //notify our user to wait a few minutes, check out a new copy and try
801
       //again.
802
        //System.out.println("file locked");
803
        MetacatReplication.replLog("lock denied for " + docid + " on " +
804
                                   server + " reason: file already locked");
805
        throw new Exception("The file specified is already locked by another " +
806
                            "user.  Please wait 30 seconds, checkout the " +
807
                            "newer document, merge your changes and try " +
808
                            "again.");
809
      }
810
      else if(openingtag.equals("<outdatedfile>"))
811
      {//our file is outdated.  notify our user to check out a new copy of the
812
       //file and merge his version with the new version.
813
        //System.out.println("outdated file");
814
        MetacatReplication.replLog("lock denied for " + docid + " on " +
815
                                    server + " reason: local file outdated");
816
        throw new Exception("The file you are trying to update is an outdated" +
817
                            " version.  Please checkout the newest document, " +
818
                            "merge your changes and try again.");
819
      }
820
    }
821
    
822
    if ( action.equals("UPDATE") ) {
823
      // check for 'write' permission for 'user' to update this document
824
      if ( !hasPermission(conn, user, group, docid) ) {
825
        throw new Exception("User " + user + 
826
              " does not have permission to update XML Document #" + docid);
827
      }          
828
    }
829

    
830
    try 
831
    {
832
      XMLReader parser=initializeParser(conn,action,newdocid,user,serverCode,dtd);
833
      conn.setAutoCommit(false);
834
      parser.parse(new InputSource(xml));
835
      conn.commit();
836
      if ( acl != null ) 
837
      {
838
        if ( action.equals("UPDATE") )  
839
        {
840
          Statement stmt = conn.createStatement();
841
          stmt.execute("DELETE FROM xml_access WHERE docid='"+newdocid +"'");
842
          stmt.close();
843
        }
844
        AccessControlList aclobj = new AccessControlList(conn, newdocid, acl);
845
        conn.commit();
846
      }
847
      conn.setAutoCommit(true);
848

    
849
    } 
850
    catch (Exception e) 
851
    {
852
      conn.rollback();
853
      conn.setAutoCommit(true);
854
      throw e;
855
    }
856
    
857
    //force replicate out the new document to each server in our server list.
858
    if(serverCode == 1)
859
    { //start the thread to replicate this new document out to the other servers
860
      ForceReplicationHandler frh = new ForceReplicationHandler(newdocid, 
861
                                                                action);
862
    }
863
      
864
    if ( (docid != null) && !(newdocid.equals(docid)) ) 
865
    {
866
      return new String("New document ID generated:" + newdocid);
867
    } 
868
    else 
869
    {
870
      return newdocid;
871
    }
872
  }
873

    
874
  /**
875
   * Delete an XML file from the database (actually, just make it a revision
876
   * in the xml_revisions table)
877
   *
878
   * @param docid the ID of the document to be deleted from the database
879
   */
880
  public static void delete( Connection conn, String docid,
881
                                 String user, String group )
882
                throws Exception {
883

    
884
    // Determine if the docid is OK for DELETE
885
    AccessionNumber ac = new AccessionNumber(conn);
886
    String newdocid = ac.generate(docid, "DELETE");
887

    
888
    // check for 'write' permission for 'user' to delete this document
889
    if ( !hasPermission(conn, user, group, docid) ) {
890
      throw new Exception("User " + user + 
891
              " does not have permission to delete XML Document #" + docid);
892
    }
893

    
894
    conn.setAutoCommit(false);
895
    // Copy the record to the xml_revisions table
896
    DocumentImpl.archiveDocRevision( conn, docid, user );
897

    
898
    // Now delete it from the xml_documents table
899
    Statement stmt = conn.createStatement();
900
    stmt.execute("DELETE FROM xml_index WHERE docid = '" + docid + "'");
901
    stmt.execute("DELETE FROM xml_documents WHERE docid = '" + docid + "'");
902
    stmt.close();
903
    conn.commit();
904
    conn.setAutoCommit(true);
905
  }
906
  
907
  /** 
908
    * Check for "WRITE" permission on @docid for @user and/or @group 
909
    * from DB connection 
910
    */
911
  private static boolean hasPermission( Connection conn, String user,
912
                                        String group, String docid) 
913
                         throws SQLException 
914
  {
915
    // b' of the command line invocation
916
    if ( (user == null) && (group == null) ) {
917
      return true;
918
    }
919

    
920
    // Check for WRITE permission on @docid for @user and/or @group
921
    AccessControlList aclobj = new AccessControlList(conn);
922
    boolean hasPermission = aclobj.hasPermission("WRITE",user,docid);
923
    if ( !hasPermission && group != null ) {
924
      hasPermission = aclobj.hasPermission("WRITE",group,docid);
925
    }
926
    
927
    return hasPermission;
928
  }
929

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

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

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

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

    
969
    return parser;
970
  }
971

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

    
994
  }
995

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

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

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

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

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