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-05-24 10:21:13 -0700 (Thu, 24 May 2001) $'
11
 * '$Revision: 752 $'
12
 *
13
 * This program is free software; you can redistribute it and/or modify
14
 * it under the terms of the GNU General Public License as published by
15
 * the Free Software Foundation; either version 2 of the License, or
16
 * (at your option) any later version.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
 * GNU General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU General Public License
24
 * along with this program; if not, write to the Free Software
25
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26
 */
27

    
28
package edu.ucsb.nceas.metacat;
29

    
30
import java.sql.*;
31
import java.io.File;
32
import java.io.FileReader;
33
import java.io.IOException;
34
import java.io.PrintWriter;
35
import java.io.Reader;
36
import java.io.StringWriter;
37
import java.io.Writer;
38
import java.io.InputStreamReader;
39

    
40
import java.util.Iterator;
41
import java.util.Stack;
42
import java.util.TreeSet;
43
import java.util.Enumeration;
44

    
45
import org.xml.sax.AttributeList;
46
import org.xml.sax.ContentHandler;
47
import org.xml.sax.DTDHandler;
48
import org.xml.sax.EntityResolver;
49
import org.xml.sax.ErrorHandler;
50
import org.xml.sax.InputSource;
51
import org.xml.sax.XMLReader;
52
import org.xml.sax.SAXException;
53
import org.xml.sax.SAXParseException;
54
import org.xml.sax.helpers.XMLReaderFactory;
55

    
56
import java.net.URL;
57

    
58
import edu.ucsb.nceas.dbadapter.DBAdapter;
59

    
60
/**
61
 * A class that represents an XML document. It can be created with a simple
62
 * document identifier from a database connection.  It also will write an
63
 * XML text document to a database connection using SAX.
64
 */
65
public class DocumentImpl {
66

    
67
  static final int ALL = 1;
68
  static final int WRITE = 2;
69
  static final int READ = 4;
70
  private static final DBAdapter dbAdapter = MetaCatUtil.dbAdapter;
71

    
72
  private Connection conn = null;
73
  private String docid = null;
74
  private String docname = null;
75
  private String doctype = null;
76
// DOCTITLE attr cleared from the db
77
//  private String doctitle = null;
78
  private String createdate = null;
79
  private String updatedate = null;
80
  private String system_id = null;
81
  private String userowner = null;
82
  private String userupdated = null;
83
  private int rev;
84
  private int serverlocation;
85
  private String publicaccess; 
86
  private long rootnodeid;
87
  private ElementNode rootNode = null;
88
  private TreeSet nodeRecordList = null;
89

    
90
  /**
91
   * Constructor, creates document from database connection, used 
92
   * for reading the document
93
   *
94
   * @param conn the database connection from which to read the document
95
   * @param docid the identifier of the document to be created
96
   */
97
  public DocumentImpl(Connection conn, String docid) throws McdbException 
98
  {
99
    try { 
100
      this.conn = conn;
101
      this.docid = docid;
102
      
103
      DocumentIdentifier id = new DocumentIdentifier(docid);
104
      
105
  
106
      // Look up the document information
107
      getDocumentInfo(docid);
108
      
109
      // Download all of the document nodes using a single SQL query
110
      // The sort order of the records is determined by the NodeComparator
111
      // class, and needs to represent a depth-first traversal for the
112
      // toXml() method to work properly
113
      nodeRecordList = getNodeRecordList(rootnodeid);
114
  
115
    } catch (McdbException ex) {
116
      throw ex;
117
    } catch (Throwable t) {
118
      throw new McdbException("Error reading document from " +
119
                              "DocumentImpl.DocumentImpl: " + docid);
120
    }
121
  }
122

    
123
  /** 
124
   * Construct a new document instance, writing the contents to the database.
125
   * This method is called from DBSAXHandler because we need to know the
126
   * root element name for documents without a DOCTYPE before creating it.
127
   *
128
   * @param conn the JDBC Connection to which all information is written
129
   * @param rootnodeid - sequence id of the root node in the document
130
   * @param docname - the name of DTD, i.e. the name immediately following 
131
   *        the DOCTYPE keyword ( should be the root element name ) or
132
   *        the root element name if no DOCTYPE declaration provided
133
   *        (Oracle's and IBM parsers are not aware if it is not the 
134
   *        root element name)
135
   * @param doctype - Public ID of the DTD, i.e. the name immediately 
136
   *                  following the PUBLIC keyword in DOCTYPE declaration or
137
   *                  the docname if no Public ID provided or
138
   *                  null if no DOCTYPE declaration provided
139
   * @param docid the docid to use for the INSERT OR UPDATE
140
   * @param action the action to be performed (INSERT OR UPDATE)
141
   * @param user the user that owns the document
142
   * @param pub flag for public "read" access on document
143
   * @param serverCode the serverid from xml_replication on which this document
144
   *        resides.
145
   *
146
   */
147
  public DocumentImpl(Connection conn, long rootnodeid, String docname, 
148
                      String doctype, String docid, String action, String user,
149
                      String pub, String catalogid, int serverCode)
150
                      throws SQLException, Exception
151
  {
152
    this.conn = conn;
153
    this.rootnodeid = rootnodeid;
154
    this.docname = docname;
155
    this.doctype = doctype;
156
    this.docid = docid;
157
    writeDocumentToDB(action, user, pub, catalogid, serverCode);
158
  }
159
  
160
// NOT USED ANY MORE
161
//  public DocumentImpl(Connection conn, long rootnodeid, String docname, 
162
//                      String doctype, String docid, String action, String user)
163
//                      throws SQLException, Exception
164
//  {
165
//    this.conn = conn;
166
//    this.rootnodeid = rootnodeid;
167
//    this.docname = docname;
168
//    this.doctype = doctype;
169
//    this.docid = docid;
170
//    writeDocumentToDB(action, user);
171
//  }
172

    
173
  /**
174
   * get the document name
175
   */
176
  public String getDocname() {
177
    return docname;
178
  }
179

    
180
  /**
181
   * get the document type (which is the PublicID)
182
   */
183
  public String getDoctype() {
184
    return doctype;
185
  }
186

    
187
  /**
188
   * get the system identifier
189
   */
190
  public String getSystemID() {
191
    return system_id;
192
  }
193

    
194
  /**
195
   * get the root node identifier
196
   */
197
  public long getRootNodeID() {
198
    return rootnodeid;
199
  }
200
  
201
  /**
202
   * get the creation date
203
   */
204
  public String getCreateDate() {
205
    return createdate;
206
  }
207
  
208
  /**
209
   * get the update date
210
   */
211
  public String getUpdateDate() {
212
    return updatedate;
213
  }
214

    
215
  /** 
216
   * Get the document identifier (docid)
217
   */
218
  public String getDocID() {
219
    return docid;
220
  }
221
  
222
// DOCTITLE attr cleared from the db
223
//  /**
224
//   *get the document title
225
//   */
226
//  public String getDocTitle() {
227
//    return doctitle;
228
//  }
229
  
230
  public String getUserowner() {
231
    return userowner;
232
  }
233
  
234
  public String getUserupdated() {
235
    return userupdated;
236
  }
237
  
238
  public int getServerlocation() {
239
    return serverlocation;
240
  }
241
  
242
  public String getPublicaccess() {
243
    return publicaccess;
244
  }
245
  
246
  public int getRev() {
247
    return rev;
248
  }
249

    
250
  /**
251
   * Print a string representation of the XML document
252
   */
253
  public String toString()
254
  {
255
    StringWriter docwriter = new StringWriter();
256
    this.toXml(docwriter);
257
    String document = docwriter.toString();
258
    return document;
259
  }
260

    
261
  /**
262
   * Get a text representation of the XML document as a string
263
   * This older algorithm uses a recursive tree of Objects to represent the
264
   * nodes of the tree.  Each object is passed the data for the document 
265
   * and searches all of the document data to find its children nodes and
266
   * recursively build.  Thus, because each node reads the whole document,
267
   * this algorithm is extremely slow for larger documents, and the time
268
   * to completion is O(N^N) wrt the number of nodes.  See toXml() for a
269
   * better algorithm.
270
   */
271
  public String readUsingSlowAlgorithm()
272
  {
273
    StringBuffer doc = new StringBuffer();
274

    
275
    // Create the elements from the downloaded data in the TreeSet
276
    rootNode = new ElementNode(nodeRecordList, rootnodeid);
277

    
278
    // Append the resulting document to the StringBuffer and return it
279
    doc.append("<?xml version=\"1.0\"?>\n");
280
      
281
    if (docname != null) {
282
      if ((doctype != null) && (system_id != null)) {
283
        doc.append("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
284
                   "\" \"" + system_id + "\">\n");
285
      } else {
286
        doc.append("<!DOCTYPE " + docname + ">\n");
287
      }
288
    }
289
    doc.append(rootNode.toString());
290
  
291
    return (doc.toString());
292
  }
293

    
294
  /**
295
   * Print a text representation of the XML document to a Writer
296
   *
297
   * @param pw the Writer to which we print the document
298
   */
299
  public void toXml(Writer pw)
300
  {
301
    PrintWriter out = null;
302
    if (pw instanceof PrintWriter) {
303
      out = (PrintWriter)pw;
304
    } else {
305
      out = new PrintWriter(pw);
306
    }
307

    
308
    MetaCatUtil util = new MetaCatUtil();
309
    
310
    Stack openElements = new Stack();
311
    boolean atRootElement = true;
312
    boolean previousNodeWasElement = false;
313

    
314
    // Step through all of the node records we were given
315
    Iterator it = nodeRecordList.iterator();
316
    while (it.hasNext()) {
317
      NodeRecord currentNode = (NodeRecord)it.next();
318
      //util.debugMessage("[Got Node ID: " + currentNode.nodeid +
319
                          //" (" + currentNode.parentnodeid +
320
                          //", " + currentNode.nodeindex + 
321
                          //", " + currentNode.nodetype + 
322
                          //", " + currentNode.nodename + 
323
                          //", " + currentNode.nodedata + ")]");
324

    
325
      // Print the end tag for the previous node if needed
326
      //
327
      // This is determined by inspecting the parent nodeid for the
328
      // currentNode.  If it is the same as the nodeid of the last element
329
      // that was pushed onto the stack, then we are still in that previous
330
      // parent element, and we do nothing.  However, if it differs, then we
331
      // have returned to a level above the previous parent, so we go into
332
      // a loop and pop off nodes and print out their end tags until we get
333
      // the node on the stack to match the currentNode parentnodeid
334
      //
335
      // So, this of course means that we rely on the list of elements
336
      // having been sorted in a depth first traversal of the nodes, which
337
      // is handled by the NodeComparator class used by the TreeSet
338
      if (!atRootElement) {
339
        NodeRecord currentElement = (NodeRecord)openElements.peek();
340
        if ( currentNode.parentnodeid != currentElement.nodeid ) {
341
          while ( currentNode.parentnodeid != currentElement.nodeid ) {
342
            currentElement = (NodeRecord)openElements.pop();
343
            util.debugMessage("\n POPPED: " + currentElement.nodename);
344
            if (previousNodeWasElement) {
345
              out.print(">");
346
              previousNodeWasElement = false;
347
            }  
348
            out.print("</" + currentElement.nodename + ">" );
349
            currentElement = (NodeRecord)openElements.peek();
350
          }
351
        }
352
      }
353

    
354
      // Handle the DOCUMENT node
355
      if (currentNode.nodetype.equals("DOCUMENT")) {
356
        out.println("<?xml version=\"1.0\"?>");
357
      
358
        if (docname != null) {
359
          if ((doctype != null) && (system_id != null)) {
360
            out.println("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
361
                       "\" \"" + system_id + "\">");
362
          } else {
363
            out.println("<!DOCTYPE " + docname + ">");
364
          }
365
        }
366

    
367
      // Handle the ELEMENT nodes
368
      } else if (currentNode.nodetype.equals("ELEMENT")) {
369
        if (atRootElement) {
370
          atRootElement = false;
371
        } else {
372
          if (previousNodeWasElement) {
373
            out.print(">");
374
          }
375
        }
376
        openElements.push(currentNode);
377
        util.debugMessage("\n PUSHED: " + currentNode.nodename);
378
        previousNodeWasElement = true;
379
        out.print("<" + currentNode.nodename);
380

    
381
      // Handle the ATTRIBUTE nodes
382
      } else if (currentNode.nodetype.equals("ATTRIBUTE")) {
383
        out.print(" " + currentNode.nodename + "=\""
384
                 + currentNode.nodedata + "\"");
385
      } else if (currentNode.nodetype.equals("TEXT")) {
386
        if (previousNodeWasElement) {
387
          out.print(">");
388
        }
389
        out.print(currentNode.nodedata);
390
        previousNodeWasElement = false;
391

    
392
      // Handle the COMMENT nodes
393
      } else if (currentNode.nodetype.equals("COMMENT")) {
394
        if (previousNodeWasElement) {
395
          out.print(">");
396
        }
397
        out.print("<!--" + currentNode.nodedata + "-->");
398
        previousNodeWasElement = false;
399

    
400
      // Handle the PI nodes
401
      } else if (currentNode.nodetype.equals("PI")) {
402
        if (previousNodeWasElement) {
403
          out.print(">");
404
        }
405
        out.print("<?" + currentNode.nodename + " " +
406
                        currentNode.nodedata + "?>");
407
        previousNodeWasElement = false;
408

    
409
      // Handle any other node type (do nothing)
410
      } else {
411
        // Any other types of nodes are not handled.
412
        // Probably should throw an exception here to indicate this
413
      }
414
      out.flush();
415
    }
416

    
417
    // Print the final end tag for the root element
418
    while(!openElements.empty())
419
    {
420
      NodeRecord currentElement = (NodeRecord)openElements.pop();
421
      util.debugMessage("\n POPPED: " + currentElement.nodename);
422
      out.print("</" + currentElement.nodename + ">" );
423
    }
424
    out.flush();
425
  }
426
  
427
  private boolean isRevisionOnly(DocumentIdentifier docid) throws Exception
428
  {
429
    //System.out.println("inRevisionOnly");
430
    PreparedStatement pstmt;
431
    String rev = docid.getRev();
432
    String newid = docid.getIdentifier();
433
    pstmt = conn.prepareStatement("select rev from xml_documents " +
434
                                  "where docid like '" + newid + "'");
435
    pstmt.execute();
436
    ResultSet rs = pstmt.getResultSet();
437
    boolean tablehasrows = rs.next();
438
    if(rev.equals("newest") || rev.equals("all"))
439
    {
440
      return false;
441
    }
442
    
443
    if(tablehasrows)
444
    {
445
      int r = rs.getInt(1);
446
      pstmt.close();
447
      if(new Integer(rev).intValue() == r)
448
      { //the current revision in in xml_documents
449
        //System.out.println("returning false");
450
        return false;
451
      }
452
      else if(new Integer(rev).intValue() < r)
453
      { //the current revision is in xml_revisions.
454
        //System.out.println("returning true");
455
        return true;
456
      }
457
      else if(new Integer(rev).intValue() > r)
458
      { //error, rev cannot be greater than r
459
        throw new Exception("requested revision cannot be greater than " +
460
                            "the latest revision number.");
461
      }
462
    }
463
    throw new Exception("the requested docid '" + docid.toString() + 
464
                        "' does not exist");
465
  }
466

    
467
  private void getDocumentInfo(String docid) throws McdbException, 
468
                                                    AccessionNumberException
469
  {
470
    getDocumentInfo(new DocumentIdentifier(docid));
471
  }
472
  
473
  /**
474
   * Look up the document type information from the database
475
   *
476
   * @param docid the id of the document to look up
477
   */
478
  private void getDocumentInfo(DocumentIdentifier docid) throws McdbException 
479
  {
480
    PreparedStatement pstmt;
481
    String table = "xml_documents";
482
    
483
    try
484
    {
485
      if(isRevisionOnly(docid))
486
      { //pull the document from xml_revisions instead of from xml_documents;
487
        table = "xml_revisions";
488
      }
489
    }
490
    catch(Exception e)
491
    {
492
      System.out.println("error in DocumentImpl.getDocumentInfo: " + 
493
                          e.getMessage());
494
    }
495
    
496
    //deal with the key words here.
497
    
498
    if(docid.getRev().equals("all"))
499
    {
500
      
501
    }
502
    
503
    try {
504
      StringBuffer sql = new StringBuffer();
505
// DOCTITLE attr cleared from the db
506
//      sql.append("SELECT docname, doctype, rootnodeid, doctitle, ");
507
      sql.append("SELECT docname, doctype, rootnodeid, ");
508
      sql.append("date_created, date_updated, user_owner, user_updated, ");
509
      sql.append("server_location, public_access, rev");
510
      sql.append(" FROM ").append(table);
511
      sql.append(" WHERE docid LIKE '").append(docid.getIdentifier());
512
      sql.append("' and rev like '").append(docid.getRev()).append("'");
513
      //System.out.println(sql.toString());
514
      pstmt =
515
        conn.prepareStatement(sql.toString());
516
      // Bind the values to the query
517
      //pstmt.setString(1, docid.getIdentifier());
518
      //pstmt.setString(2, docid.getRev());
519

    
520
      pstmt.execute();
521
      ResultSet rs = pstmt.getResultSet();
522
      boolean tableHasRows = rs.next();
523
      if (tableHasRows) {
524
        this.docname        = rs.getString(1);
525
        this.doctype        = rs.getString(2);
526
        this.rootnodeid     = rs.getLong(3);
527
// DOCTITLE attr cleared from the db
528
//        this.doctitle       = rs.getString(4);
529
        this.createdate     = rs.getString(4);
530
        this.updatedate     = rs.getString(5);
531
        this.userowner      = rs.getString(6);
532
        this.userupdated    = rs.getString(7);
533
        this.serverlocation = rs.getInt(8);
534
        this.publicaccess   = rs.getString(9);
535
        this.rev            = rs.getInt(10);
536
      } 
537

    
538
      if (this.doctype != null) {
539
        pstmt =
540
          conn.prepareStatement("SELECT system_id " +
541
                                  "FROM xml_catalog " +
542
                                 "WHERE public_id LIKE ?");
543
        // Bind the values to the query
544
        pstmt.setString(1, doctype);
545
  
546
        pstmt.execute();
547
        rs = pstmt.getResultSet();
548
        tableHasRows = rs.next();
549
        if (tableHasRows) {
550
          this.system_id  = rs.getString(1);
551
        } 
552
        //pstmt.close();
553
      }
554
    } catch (SQLException e) {
555
      System.out.println("error in DocumentImpl.getDocumentInfo: " + 
556
                          e.getMessage());
557
      e.printStackTrace(System.out);
558
      throw new McdbException("Error accessing database connection in " +
559
                              "DocumentImpl.getDocumentInfo: ", e);
560
    }
561

    
562
    if (this.docname == null) {
563
      throw new McdbDocNotFoundException("Document not found: " + docid);
564
    }
565
  }
566

    
567
  /**
568
   * Look up the node data from the database
569
   *
570
   * @param rootnodeid the id of the root node of the node tree to look up
571
   */
572
  private TreeSet getNodeRecordList(long rootnodeid) throws McdbException 
573
  {
574
    PreparedStatement pstmt;
575
    TreeSet nodeRecordList = new TreeSet(new NodeComparator());
576
    long nodeid = 0;
577
    long parentnodeid = 0;
578
    long nodeindex = 0;
579
    String nodetype = null;
580
    String nodename = null;
581
    String nodedata = null;
582

    
583
    try {
584
      pstmt =
585
      conn.prepareStatement("SELECT nodeid,parentnodeid,nodeindex, " +
586
           "nodetype,nodename,"+               
587
           "replace(" +
588
           "replace(" +
589
           "replace(nodedata,'&','&amp;') " +
590
           ",'<','&lt;') " +
591
           ",'>','&gt;') " +
592
           "FROM xml_nodes WHERE rootnodeid = ?");
593

    
594
      // Bind the values to the query
595
      pstmt.setLong(1, rootnodeid);
596

    
597
      pstmt.execute();
598
      ResultSet rs = pstmt.getResultSet();
599
      boolean tableHasRows = rs.next();
600
      while (tableHasRows) {
601
        nodeid = rs.getLong(1);
602
        parentnodeid = rs.getLong(2);
603
        nodeindex = rs.getLong(3);
604
        nodetype = rs.getString(4);
605
        nodename = rs.getString(5);
606
        nodedata = rs.getString(6);
607

    
608
        // add the data to the node record list hashtable
609
        NodeRecord currentRecord = new NodeRecord(nodeid, parentnodeid, 
610
                                   nodeindex, nodetype, nodename, nodedata);
611
        nodeRecordList.add(currentRecord);
612

    
613
        // Advance to the next node
614
        tableHasRows = rs.next();
615
      } 
616
      pstmt.close();
617

    
618
    } catch (SQLException e) {
619
      throw new McdbException("Error accessing database connection from " +
620
                              "DocumentImpl.getNodeRecordList ", e);
621
    }
622

    
623
    if (nodeRecordList != null) {
624
      return nodeRecordList;
625
    } else {
626
      throw new McdbException("Error getting node data: " + docid);
627
    }
628
  }
629
  
630
// NOT USED ANY MORE
631
//  /** creates SQL code and inserts new document into DB connection 
632
//   default serverCode of 1*/
633
//  private void writeDocumentToDB(String action, String user)
634
//               throws SQLException, Exception
635
//  {
636
//    writeDocumentToDB(action, user, null, 1);
637
//  }
638

    
639
 /** creates SQL code and inserts new document into DB connection */
640
  private void writeDocumentToDB(String action, String user, String pub, 
641
                                 String catalogid, int serverCode) 
642
               throws SQLException, Exception {
643

    
644
    String sysdate = dbAdapter.getDateFunction();
645

    
646
    try {
647
      PreparedStatement pstmt = null;
648

    
649
      if (action.equals("INSERT")) {
650
        //AccessionNumber ac = new AccessionNumber();
651
        //this.docid = ac.generate(docid, "INSERT");
652
        pstmt = conn.prepareStatement(
653
                "INSERT INTO xml_documents " +
654
                "(docid, rootnodeid, docname, doctype, " + 
655
                "user_owner, user_updated, date_created, date_updated, " + 
656
                "public_access, catalog_id, server_location) " +
657
                "VALUES (?, ?, ?, ?, ?, ?, " + sysdate + ", " + sysdate + 
658
                ", ?, ?, ?)");
659
        //note that the server_location is set to 1. 
660
        //this means that "localhost" in the xml_replication table must
661
        //always be the first entry!!!!!
662
        
663
        // Bind the values to the query
664
        pstmt.setString(1, this.docid);
665
        pstmt.setLong(2, rootnodeid);
666
        pstmt.setString(3, docname);
667
        pstmt.setString(4, doctype);
668
        pstmt.setString(5, user);
669
        pstmt.setString(6, user);
670
        if ( pub == null ) {
671
          pstmt.setString(7, null);
672
        } else if ( pub.toUpperCase().equals("YES") || pub.equals("1") ) {
673
          pstmt.setInt(7, 1);
674
        } else if ( pub.toUpperCase().equals("NO") || pub.equals("0") ) {
675
          pstmt.setInt(7, 0);
676
        }
677
        pstmt.setString(8, catalogid);
678
        pstmt.setInt(9, serverCode);
679
      } else if (action.equals("UPDATE")) {
680

    
681
        // Save the old document entry in a backup table
682
        DocumentImpl.archiveDocRevision( conn, docid, user );
683
        DocumentImpl thisdoc = new DocumentImpl(conn, docid);
684
        int thisrev = thisdoc.getRev();
685
        thisrev++;
686
        // Delete index for the old version of docid
687
        // The new index is inserting on the next calls to DBSAXNode
688
        pstmt = conn.prepareStatement(
689
                "DELETE FROM xml_index WHERE docid='" + this.docid + "'");
690
        pstmt.execute();
691
        //pstmt.close();
692

    
693
        // Update the new document to reflect the new node tree
694
        pstmt = conn.prepareStatement(
695
            "UPDATE xml_documents " +
696
            "SET rootnodeid = ?, docname = ?, doctype = ?, " +
697
            "user_updated = ?, date_updated = " + sysdate + ", " +
698
            "server_location = ?, rev = ?, public_access = ?, catalog_id = ? " +
699
            "WHERE docid LIKE ?");
700
        // Bind the values to the query
701
        pstmt.setLong(1, rootnodeid);
702
        pstmt.setString(2, docname);
703
        pstmt.setString(3, doctype);
704
        pstmt.setString(4, user);
705
        pstmt.setInt(5, serverCode);
706
        pstmt.setInt(6, thisrev);
707
        if ( pub == null ) {
708
          pstmt.setString(7, null);
709
        } else if ( pub.toUpperCase().equals("YES") || pub.equals("1") ) {
710
          pstmt .setInt(7, 1);
711
        } else if ( pub.toUpperCase().equals("NO") || pub.equals("0") ) {
712
          pstmt.setInt(7, 0);
713
        }
714
        pstmt.setString(8, catalogid);
715
        pstmt.setString(9, this.docid);
716

    
717
      } else {
718
        System.err.println("Action not supported: " + action);
719
      }
720

    
721
      // Do the insertion
722
      pstmt.execute();
723
      pstmt.close();
724

    
725
    } catch (SQLException sqle) {
726
      throw sqle;
727
    } catch (Exception e) {
728
      throw e;
729
    }
730
  }
731

    
732
  /**
733
   * Write an XML file to the database, given a filename
734
   *
735
   * @param conn the JDBC connection to the database
736
   * @param filename the filename to be loaded into the database
737
   * @param pub flag for public "read" access on document
738
   * @param dtdfilename the dtd to be uploaded on server's file system
739
   * @param action the action to be performed (INSERT OR UPDATE)
740
   * @param docid the docid to use for the INSERT OR UPDATE
741
   * @param user the user that owns the document
742
   * @param group the group to which user belongs
743
   */
744
  public static String write(Connection conn,String filename,
745
                             String pub, String dtdfilename,
746
                             String action, String docid, String user,
747
                             String group )
748
                throws Exception {
749
                  
750
    Reader dtd = null;
751
    if ( dtdfilename != null ) {
752
      dtd = new FileReader(new File(dtdfilename).toString());
753
    }
754
    return write ( conn, new FileReader(new File(filename).toString()),
755
                   pub, dtd, action, docid, user, group, false);
756
  }
757

    
758
  public static String write(Connection conn,Reader xml,String pub,Reader dtd,
759
                             String action, String docid, String user,
760
                             String group, boolean validate)
761
                throws Exception {
762
    return write(conn,xml,pub,dtd,action,docid,user,group,1,false,validate);
763
  }
764

    
765
  public static String write(Connection conn, Reader xml, String pub,
766
                             String action, String docid, String user,
767
                             String group )
768
                throws Exception {
769
    if(action.equals("UPDATE"))
770
    {//if the document is being updated then use the servercode from the 
771
     //originally inserted document.
772
      DocumentImpl doc = new DocumentImpl(conn, docid);
773
      int servercode = doc.getServerlocation();
774
      return write(conn, xml, pub, action, docid, user, group, servercode);
775
    }
776
    else
777
    {//if the file is being inserted then the servercode is always 1
778
      return write(conn, xml, pub, action, docid, user, group, 1);
779
    }
780
  }
781
  
782
  public static String write( Connection conn, Reader xml,
783
                              String action, String docid, String user,
784
                              String group, int serverCode )
785
                throws Exception
786
  {
787
    return write(conn,xml,null,action,docid,user,group,serverCode);
788
  }
789
  
790
  public static String write( Connection conn, Reader xml, String pub,
791
                              String action, String docid, String user,
792
                              String group, int serverCode) 
793
                throws Exception
794
  {
795
    return write(conn,xml,pub,null,action,docid,user,group,
796
                 serverCode,false,false);
797
  }
798
  
799
  public static String write( Connection conn, Reader xml, String pub,
800
                              String action, String docid, String user,
801
                              String group, int serverCode, boolean override)
802
                throws Exception
803
  {
804
    return write(conn,xml,pub,null,action,docid,user,group,
805
                 serverCode,override,false);
806
  }
807
  
808
  /**
809
   * Write an XML file to the database, given a Reader
810
   *
811
   * @param conn the JDBC connection to the database
812
   * @param xml the xml stream to be loaded into the database
813
   * @param pub flag for public "read" access on xml document
814
   * @param dtd the dtd to be uploaded on server's file system
815
   * @param action the action to be performed (INSERT or UPDATE)
816
   * @param accnum the docid + rev# to use on INSERT or UPDATE
817
   * @param user the user that owns the document
818
   * @param group the group to which user belongs
819
   * @param serverCode the serverid from xml_replication on which this document
820
   *        resides.
821
   * @param override flag to stop insert replication checking.
822
   *        if override = true then a document not belonging to the local server
823
   *        will not be checked upon update for a file lock.
824
   *        if override = false then a document not from this server, upon 
825
   *        update will be locked and version checked.
826
   */
827

    
828
  public static String write( Connection conn,Reader xml,String pub,Reader dtd,
829
                              String action, String accnum, String user,
830
                              String group, int serverCode, boolean override,
831
                              boolean validate)
832
                throws Exception
833
  {
834
    int rev = 1;
835
    String docid = null;
836
    MetaCatUtil util = new MetaCatUtil();
837
    String sep = util.getOption("accNumSeparator");
838
    
839
    if ( accnum != null ) {
840
      // check the correctness of accnum;
841
      // split accnum in docid and rev in order to
842
      // preserve the current implementation of processing and storing,
843
      // but show the whole accnum to the client.
844
      DocumentIdentifier id = new DocumentIdentifier(accnum);
845
      docid = id.getIdentifier();
846
      rev = (new Integer(id.getRev())).intValue();
847
      sep = id.getSeparator();
848
    }
849
    
850
    // Determine if the docid,rev are OK for INSERT or UPDATE
851
    // Generate new docid on INSERT, if one is not provided.
852
    AccessionNumber ac = new AccessionNumber(conn);
853
    docid = ac.generate(docid, java.lang.String.valueOf(rev), action); 
854
    
855
    MetaCatUtil.debugMessage("action: " + action + " servercode: " + 
856
                             serverCode + " override: " + override);
857
                        
858
    if((serverCode != 1 && action.equals("UPDATE")) && !override)
859
    { //if this document being written is not a resident of this server then
860
      //we need to try to get a lock from it's resident server.  If the
861
      //resident server will not give a lock then we send the user a message
862
      //saying that he/she needs to download a new copy of the file and
863
      //merge the differences manually.
864
      int istreamInt; 
865
      char istreamChar;
866
      // NOT NEEDED
867
      //DocumentImpl newdoc = new DocumentImpl(conn, docid);
868
      //updaterev = newdoc.getRev();
869
      //String updaterev = rev;
870
      String server = MetacatReplication.getServer(serverCode);
871
      MetacatReplication.replLog("attempting to lock " + accnum);
872
      URL u = new URL("http://" + server + "?action=getlock&updaterev=" + 
873
                      rev + "&docid=" + docid);
874
      System.out.println("sending message: " + u.toString());
875
      String serverResStr = MetacatReplication.getURLContent(u);
876
      String openingtag = serverResStr.substring(0, serverResStr.indexOf(">")+1);
877
      
878
      if(openingtag.equals("<lockgranted>"))
879
      {//the lock was granted go ahead with the insert
880
        try 
881
        {
882
          MetacatReplication.replLog("lock granted for " + accnum + " from " +
883
                                      server);
884
          XMLReader parser = initializeParser(conn, action, docid, validate,
885
                                              user, group, pub, serverCode, dtd);
886
          conn.setAutoCommit(false);
887
          parser.parse(new InputSource(xml)); 
888
          conn.commit();
889
          conn.setAutoCommit(true);
890
        } 
891
        catch (Exception e) 
892
        {
893
          conn.rollback();
894
          conn.setAutoCommit(true);
895
          throw e;
896
        }
897
                
898
        //after inserting the document locally, tell the document's home server
899
        //to come get a copy from here.
900
        ForceReplicationHandler frh = new ForceReplicationHandler(docid);
901
        
902
        rev++;
903
        return (docid + sep + rev);
904
      }
905

    
906
      else if(openingtag.equals("<filelocked>"))
907
      {//the file is currently locked by another user
908
       //notify our user to wait a few minutes, check out a new copy and try
909
       //again.
910
        //System.out.println("file locked");
911
        MetacatReplication.replLog("lock denied for " + accnum + " on " +
912
                                   server + " reason: file already locked");
913
        throw new Exception("The file specified is already locked by another " +
914
                            "user.  Please wait 30 seconds, checkout the " +
915
                            "newer document, merge your changes and try " +
916
                            "again.");
917
      }
918
      else if(openingtag.equals("<outdatedfile>"))
919
      {//our file is outdated.  notify our user to check out a new copy of the
920
       //file and merge his version with the new version.
921
        //System.out.println("outdated file");
922
        MetacatReplication.replLog("lock denied for " + accnum + " on " +
923
                                    server + " reason: local file outdated");
924
        throw new Exception("The file you are trying to update is an outdated" +
925
                            " version.  Please checkout the newest document, " +
926
                            "merge your changes and try again.");
927
      }
928
    }
929
    
930
    if ( action.equals("UPDATE") ) {
931
      // check for 'write' permission for 'user' to update this document
932

    
933
      if ( !hasPermission(conn, user, group, docid) ) {
934
        throw new Exception("User " + user + 
935
              " does not have permission to update XML Document #" + accnum);
936
      }          
937
      rev++;
938
    }
939

    
940
    try 
941
    { 
942
      XMLReader parser = initializeParser(conn, action, docid, validate,
943
                                          user, group, pub, serverCode, dtd);
944
      conn.setAutoCommit(false);
945
      parser.parse(new InputSource(xml));
946
      conn.commit();
947
      conn.setAutoCommit(true);
948
    } 
949
    catch (Exception e) 
950
    {
951
      conn.rollback();
952
      conn.setAutoCommit(true);
953
      throw e;
954
    }
955
    
956
    //force replicate out the new document to each server in our server list.
957
    if(serverCode == 1)
958
    { //start the thread to replicate this new document out to the other servers
959
      ForceReplicationHandler frh = new ForceReplicationHandler(docid, action);
960
    }
961
      
962
    return (docid + sep + rev);
963
  }
964

    
965
  /**
966
   * Delete an XML file from the database (actually, just make it a revision
967
   * in the xml_revisions table)
968
   *
969
   * @param docid the ID of the document to be deleted from the database
970
   */
971
  public static void delete( Connection conn, String accnum,
972
                                 String user, String group )
973
                throws Exception 
974
  {
975
    DocumentIdentifier id = new DocumentIdentifier(accnum);
976
    String docid = id.getIdentifier();
977
    String rev = id.getRev();
978
    
979
    // Determine if the docid,rev are OK for DELETE
980
    AccessionNumber ac = new AccessionNumber(conn);
981
    docid = ac.generate(docid, rev, "DELETE");
982

    
983
    // check for 'write' permission for 'user' to delete this document
984
    if ( !hasPermission(conn, user, group, docid) ) {
985
      throw new Exception("User " + user + 
986
              " does not have permission to delete XML Document #" + accnum);
987
    }
988

    
989
    conn.setAutoCommit(false);
990
    // Copy the record to the xml_revisions table
991
    DocumentImpl.archiveDocRevision( conn, docid, user );
992

    
993
    // Now delete it from the xml_documents table
994
    
995
    Statement stmt = conn.createStatement();
996
    stmt.execute("DELETE FROM xml_index WHERE docid = '" + docid + "'");
997
    stmt.execute("DELETE FROM xml_documents WHERE docid = '" + docid + "'");
998
    //stmt.execute("DELETE FROM xml_access WHERE docid = '" + docid + "'");
999
    stmt.execute("DELETE FROM xml_access WHERE accessfileid = '" + docid + "'");
1000
    stmt.execute("DELETE FROM xml_relation WHERE docid = '" + docid + "'");
1001
    stmt.close();
1002
    conn.commit();
1003
    conn.setAutoCommit(true);
1004
    //IF this is a package document:
1005
    //delete all of the relations that this document created.
1006
    //if the deleted document is a package document its relations should 
1007
    //no longer be active if it has been deleted from the system.
1008
    
1009
  }
1010

    
1011
  /** 
1012
    * Check for "WRITE" permission on @docid for @user and/or @group 
1013
    * from DB connection 
1014
    */
1015
  private static boolean hasPermission( Connection conn, String user,
1016
                                        String group, String docid) 
1017
                         throws SQLException 
1018
  {
1019
    // b' of the command line invocation
1020
    if ( (user == null) && (group == null) ) {
1021
      return true;
1022
    }
1023

    
1024
    // Check for WRITE permission on @docid for @user and/or @group
1025
    AccessControlList aclobj = new AccessControlList(conn);
1026
    boolean hasPermission = aclobj.hasPermission("WRITE",user,docid);
1027
    if ( !hasPermission && group != null ) {
1028
      hasPermission = aclobj.hasPermission("WRITE",group,docid);
1029
    }
1030
    
1031
    return hasPermission;
1032
  }
1033

    
1034
  /**
1035
   * Set up the parser handlers for writing the document to the database
1036
   */
1037
  private static XMLReader initializeParser(Connection conn, String action,
1038
                                   String docid, boolean validate, 
1039
                                   String user, String group, String pub, 
1040
                                   int serverCode, Reader dtd) 
1041
                           throws Exception 
1042
  {
1043
    XMLReader parser = null;
1044
    //
1045
    // Set up the SAX document handlers for parsing
1046
    //
1047
    try {
1048
      ContentHandler chandler = new DBSAXHandler(conn, action, docid,
1049
                                                 user, group, pub, serverCode);
1050
      EntityResolver eresolver= new DBEntityResolver(conn,
1051
                                                 (DBSAXHandler)chandler, dtd);
1052
      DTDHandler dtdhandler   = new DBDTDHandler(conn);
1053

    
1054
      // Get an instance of the parser
1055
      MetaCatUtil util = new MetaCatUtil();
1056
      String parserName = util.getOption("saxparser");
1057
      parser = XMLReaderFactory.createXMLReader(parserName);
1058

    
1059
      // Turn on validation
1060
      parser.setFeature("http://xml.org/sax/features/validation", validate);
1061
      // Turn off Including all external parameter entities
1062
      // (the external DTD subset also)
1063
      // Doesn't work well, probably the feature name is not correct
1064
      // parser.setFeature(
1065
      //  "http://xml.org/sax/features/external-parameter-entities", false);
1066
      
1067
      // Set Handlers in the parser
1068
      parser.setProperty("http://xml.org/sax/properties/declaration-handler",
1069
                         chandler);
1070
      parser.setProperty("http://xml.org/sax/properties/lexical-handler",
1071
                         chandler);
1072
      parser.setContentHandler((ContentHandler)chandler);
1073
      parser.setEntityResolver((EntityResolver)eresolver);
1074
      parser.setDTDHandler((DTDHandler)dtdhandler);
1075
      parser.setErrorHandler((ErrorHandler)chandler);
1076

    
1077
    } catch (Exception e) {
1078
      throw e;
1079
    }
1080

    
1081
    return parser;
1082
  }
1083

    
1084
  /** Save a document entry in the xml_revisions table */
1085
  private static void archiveDocRevision(Connection conn, String docid,
1086
                                         String user) 
1087
                                         throws SQLException {
1088
    String sysdate = dbAdapter.getDateFunction();
1089
    
1090
    // create a record in xml_revisions table 
1091
    // for that document as selected from xml_documents
1092
    PreparedStatement pstmt = conn.prepareStatement(
1093
      "INSERT INTO xml_revisions " +
1094
        "(revisionid, docid, rootnodeid, docname, doctype, " +
1095
        "user_owner, user_updated, date_created, date_updated, " +
1096
        "server_location, rev, public_access, catalog_id) " +
1097
      "SELECT null, ?, rootnodeid, docname, doctype, " + 
1098
        "user_owner, ?, " + sysdate + ", " + sysdate + ", "+
1099
        "server_location, rev, public_access, catalog_id " +
1100
      "FROM xml_documents " +
1101
      "WHERE docid = ?");
1102
    // Bind the values to the query and execute it
1103
    pstmt.setString(1, docid);
1104
    pstmt.setString(2, user);
1105
    pstmt.setString(3, docid);
1106
    pstmt.execute();
1107
    pstmt.close();
1108

    
1109
  }
1110

    
1111
  /**
1112
   * the main routine used to test the DBWriter utility.
1113
   * <p>
1114
   * Usage: java DocumentImpl <-f filename -a action -d docid>
1115
   *
1116
   * @param filename the filename to be loaded into the database
1117
   * @param action the action to perform (READ, INSERT, UPDATE, DELETE)
1118
   * @param docid the id of the document to process
1119
   */
1120
  static public void main(String[] args) {
1121
     
1122
    try {
1123
      String filename    = null;
1124
      String dtdfilename = null;
1125
      String action      = null;
1126
      String docid       = null;
1127
      boolean showRuntime = false;
1128
      boolean useOldReadAlgorithm = false;
1129

    
1130
      // Parse the command line arguments
1131
      for ( int i=0 ; i < args.length; ++i ) {
1132
        if ( args[i].equals( "-f" ) ) {
1133
          filename =  args[++i];
1134
        } else if ( args[i].equals( "-r" ) ) {
1135
          dtdfilename =  args[++i];
1136
        } else if ( args[i].equals( "-a" ) ) {
1137
          action =  args[++i];
1138
        } else if ( args[i].equals( "-d" ) ) {
1139
          docid =  args[++i];
1140
        } else if ( args[i].equals( "-t" ) ) {
1141
          showRuntime = true;
1142
        } else if ( args[i].equals( "-old" ) ) {
1143
          useOldReadAlgorithm = true;
1144
        } else {
1145
          System.err.println
1146
            ( "   args[" +i+ "] '" +args[i]+ "' ignored." );
1147
        }
1148
      }
1149
      
1150
      // Check if the required arguments are provided
1151
      boolean argsAreValid = false;
1152
      if (action != null) {
1153
        if (action.equals("INSERT")) {
1154
          if (filename != null) {
1155
            argsAreValid = true;
1156
          } 
1157
        } else if (action.equals("UPDATE")) {
1158
          if ((filename != null) && (docid != null)) {
1159
            argsAreValid = true;
1160
          } 
1161
        } else if (action.equals("DELETE")) {
1162
          if (docid != null) {
1163
            argsAreValid = true;
1164
          } 
1165
        } else if (action.equals("READ")) {
1166
          if (docid != null) {
1167
            argsAreValid = true;
1168
          } 
1169
        } 
1170
      } 
1171

    
1172
      // Print usage message if the arguments are not valid
1173
      if (!argsAreValid) {
1174
        System.err.println("Wrong number of arguments!!!");
1175
        System.err.println(
1176
          "USAGE: java DocumentImpl [-t] <-a INSERT> [-d docid] <-f filename> "+
1177
          "[-r dtdfilename]");
1178
        System.err.println(
1179
          "   OR: java DocumentImpl [-t] <-a UPDATE -d docid -f filename> " +
1180
          "[-r dtdfilename]");
1181
        System.err.println(
1182
          "   OR: java DocumentImpl [-t] <-a DELETE -d docid>");
1183
        System.err.println(
1184
          "   OR: java DocumentImpl [-t] [-old] <-a READ -d docid>");
1185
        return;
1186
      }
1187
      
1188
      // Time the request if asked for
1189
      double startTime = System.currentTimeMillis();
1190
      
1191
      // Open a connection to the database
1192
      MetaCatUtil util = new MetaCatUtil();
1193
      Connection dbconn = util.openDBConnection();
1194

    
1195
      double connTime = System.currentTimeMillis();
1196
      // Execute the action requested (READ, INSERT, UPDATE, DELETE)
1197
      if (action.equals("READ")) {
1198
          DocumentImpl xmldoc = new DocumentImpl( dbconn, docid );
1199
          if (useOldReadAlgorithm) {
1200
            System.out.println(xmldoc.readUsingSlowAlgorithm());
1201
          } else {
1202
            xmldoc.toXml(new PrintWriter(System.out));
1203
          }
1204
      } else if (action.equals("DELETE")) {
1205
        DocumentImpl.delete(dbconn, docid, null, null);
1206
        System.out.println("Document deleted: " + docid);
1207
      } else {
1208
        String newdocid = DocumentImpl.write(dbconn, filename, null,
1209
                                             dtdfilename, action, docid,
1210
                                             null, null);
1211
        if ((docid != null) && (!docid.equals(newdocid))) {
1212
          if (action.equals("INSERT")) {
1213
            System.out.println("New document ID generated!!! ");
1214
          } else if (action.equals("UPDATE")) {
1215
            System.out.println("ERROR: Couldn't update document!!! ");
1216
          }
1217
        } else if ((docid == null) && (action.equals("UPDATE"))) {
1218
          System.out.println("ERROR: Couldn't update document!!! ");
1219
        }
1220
        System.out.println("Document processing finished for: " + filename
1221
              + " (" + newdocid + ")");
1222
      }
1223

    
1224
      double stopTime = System.currentTimeMillis();
1225
      double dbOpenTime = (connTime - startTime)/1000;
1226
      double insertTime = (stopTime - connTime)/1000;
1227
      double executionTime = (stopTime - startTime)/1000;
1228
      if (showRuntime) {
1229
        System.out.println("\n\nTotal Execution time was: " + 
1230
                           executionTime + " seconds.");
1231
        System.out.println("Time to open DB connection was: " + dbOpenTime + 
1232
                           " seconds.");
1233
        System.out.println("Time to insert document was: " + insertTime +
1234
                           " seconds.");
1235
      }
1236
      dbconn.close();
1237
    } catch (McdbException me) {
1238
      me.toXml(new PrintWriter(System.err));
1239
    } catch (AccessionNumberException ane) {
1240
      System.out.println(ane.getMessage());
1241
    } catch (Exception e) {
1242
      System.err.println("EXCEPTION HANDLING REQUIRED");
1243
      System.err.println(e.getMessage());
1244
      e.printStackTrace(System.err);
1245
    }
1246
  }
1247
}
(26-26/43)