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-03 17:12:39 -0700 (Thu, 03 May 2001) $'
11
 * '$Revision: 734 $'
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
/**
59
 * A class that represents an XML document. It can be created with a simple
60
 * document identifier from a database connection.  It also will write an
61
 * XML text document to a database connection using SAX.
62
 */
63
public class DocumentImpl {
64

    
65
  static final int ALL = 1;
66
  static final int WRITE = 2;
67
  static final int READ = 4;
68

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

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

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

    
170
  /**
171
   * get the document name
172
   */
173
  public String getDocname() {
174
    return docname;
175
  }
176

    
177
  /**
178
   * get the document type (which is the PublicID)
179
   */
180
  public String getDoctype() {
181
    return doctype;
182
  }
183

    
184
  /**
185
   * get the system identifier
186
   */
187
  public String getSystemID() {
188
    return system_id;
189
  }
190

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

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

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

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

    
272
    // Create the elements from the downloaded data in the TreeSet
273
    rootNode = new ElementNode(nodeRecordList, rootnodeid);
274

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

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

    
305
    MetaCatUtil util = new MetaCatUtil();
306
    
307
    Stack openElements = new Stack();
308
    boolean atRootElement = true;
309
    boolean previousNodeWasElement = false;
310

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

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

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

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

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

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

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

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

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

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

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

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

    
559
    if (this.docname == null) {
560
      throw new McdbDocNotFoundException("Document not found: " + docid);
561
    }
562
  }
563

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

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

    
591
      // Bind the values to the query
592
      pstmt.setLong(1, rootnodeid);
593

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

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

    
610
        // Advance to the next node
611
        tableHasRows = rs.next();
612
      } 
613
      pstmt.close();
614

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

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

    
636
 /** creates SQL code and inserts new document into DB connection */
637
  private void writeDocumentToDB(String action, String user, String pub, 
638
                                 String catalogid, int serverCode) 
639
               throws SQLException, Exception {
640
    try {
641
      PreparedStatement pstmt = null;
642

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

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

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

    
710
      } else {
711
        System.err.println("Action not supported: " + action);
712
      }
713

    
714
      // Do the insertion
715
      pstmt.execute();
716
      pstmt.close();
717

    
718
    } catch (SQLException sqle) {
719
      throw sqle;
720
    } catch (Exception e) {
721
      throw e;
722
    }
723
  }
724

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

    
751
  public static String write(Connection conn,Reader xml,String pub,Reader dtd,
752
                             String action, String docid, String user,
753
                             String group, boolean validate)
754
                throws Exception {
755
    return write(conn,xml,pub,dtd,action,docid,user,group,1,false,validate);
756
  }
757

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

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

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

    
925
      if ( !hasPermission(conn, user, group, docid) ) {
926
        throw new Exception("User " + user + 
927
              " does not have permission to update XML Document #" + accnum);
928
      }          
929
    }
930

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

    
956
  /**
957
   * Delete an XML file from the database (actually, just make it a revision
958
   * in the xml_revisions table)
959
   *
960
   * @param docid the ID of the document to be deleted from the database
961
   */
962
  public static void delete( Connection conn, String accnum,
963
                                 String user, String group )
964
                throws Exception 
965
  {
966
    DocumentIdentifier id = new DocumentIdentifier(accnum);
967
    String docid = id.getIdentifier();
968
    String rev = id.getRev();
969
    
970
    // Determine if the docid,rev are OK for DELETE
971
    AccessionNumber ac = new AccessionNumber(conn);
972
    docid = ac.generate(docid, rev, "DELETE");
973

    
974
    // check for 'write' permission for 'user' to delete this document
975
    if ( !hasPermission(conn, user, group, docid) ) {
976
      throw new Exception("User " + user + 
977
              " does not have permission to delete XML Document #" + accnum);
978
    }
979

    
980
    conn.setAutoCommit(false);
981
    // Copy the record to the xml_revisions table
982
    DocumentImpl.archiveDocRevision( conn, docid, user );
983

    
984
    // Now delete it from the xml_documents table
985
    
986
    Statement stmt = conn.createStatement();
987
    stmt.execute("DELETE FROM xml_index WHERE docid = '" + docid + "'");
988
    stmt.execute("DELETE FROM xml_documents WHERE docid = '" + docid + "'");
989
    //stmt.execute("DELETE FROM xml_access WHERE docid = '" + docid + "'");
990
    stmt.execute("DELETE FROM xml_access WHERE accessfileid = '" + docid + "'");
991
    stmt.execute("DELETE FROM xml_relation WHERE docid = '" + docid + "'");
992
    stmt.close();
993
    conn.commit();
994
    conn.setAutoCommit(true);
995
    //IF this is a package document:
996
    //delete all of the relations that this document created.
997
    //if the deleted document is a package document its relations should 
998
    //no longer be active if it has been deleted from the system.
999
    
1000
  }
1001

    
1002
  /** 
1003
    * Check for "WRITE" permission on @docid for @user and/or @group 
1004
    * from DB connection 
1005
    */
1006
  private static boolean hasPermission( Connection conn, String user,
1007
                                        String group, String docid) 
1008
                         throws SQLException 
1009
  {
1010
    // b' of the command line invocation
1011
    if ( (user == null) && (group == null) ) {
1012
      return true;
1013
    }
1014

    
1015
    // Check for WRITE permission on @docid for @user and/or @group
1016
    AccessControlList aclobj = new AccessControlList(conn);
1017
    boolean hasPermission = aclobj.hasPermission("WRITE",user,docid);
1018
    if ( !hasPermission && group != null ) {
1019
      hasPermission = aclobj.hasPermission("WRITE",group,docid);
1020
    }
1021
    
1022
    return hasPermission;
1023
  }
1024

    
1025
  /**
1026
   * Set up the parser handlers for writing the document to the database
1027
   */
1028
  private static XMLReader initializeParser(Connection conn, String action,
1029
                                   String docid, boolean validate, 
1030
                                   String user, String group, String pub, 
1031
                                   int serverCode, Reader dtd) 
1032
                           throws Exception 
1033
  {
1034
    XMLReader parser = null;
1035
    //
1036
    // Set up the SAX document handlers for parsing
1037
    //
1038
    try {
1039
      ContentHandler chandler = new DBSAXHandler(conn, action, docid,
1040
                                                 user, group, pub, serverCode);
1041
      EntityResolver eresolver= new DBEntityResolver(conn,
1042
                                                 (DBSAXHandler)chandler, dtd);
1043
      DTDHandler dtdhandler   = new DBDTDHandler(conn);
1044

    
1045
      // Get an instance of the parser
1046
      MetaCatUtil util = new MetaCatUtil();
1047
      String parserName = util.getOption("saxparser");
1048
      parser = XMLReaderFactory.createXMLReader(parserName);
1049

    
1050
      // Turn on validation
1051
      parser.setFeature("http://xml.org/sax/features/validation", validate);
1052
      // Turn off Including all external parameter entities
1053
      // (the external DTD subset also)
1054
      // Doesn't work well, probably the feature name is not correct
1055
      // parser.setFeature(
1056
      //  "http://xml.org/sax/features/external-parameter-entities", false);
1057
      
1058
      // Set Handlers in the parser
1059
      parser.setProperty("http://xml.org/sax/properties/declaration-handler",
1060
                         chandler);
1061
      parser.setProperty("http://xml.org/sax/properties/lexical-handler",
1062
                         chandler);
1063
      parser.setContentHandler((ContentHandler)chandler);
1064
      parser.setEntityResolver((EntityResolver)eresolver);
1065
      parser.setDTDHandler((DTDHandler)dtdhandler);
1066
      parser.setErrorHandler((ErrorHandler)chandler);
1067

    
1068
    } catch (Exception e) {
1069
      throw e;
1070
    }
1071

    
1072
    return parser;
1073
  }
1074

    
1075
  /** Save a document entry in the xml_revisions table */
1076
  private static void archiveDocRevision(Connection conn, String docid,
1077
                                         String user) 
1078
                                         throws SQLException {
1079
    // create a record in xml_revisions table 
1080
    // for that document as selected from xml_documents
1081
    PreparedStatement pstmt = conn.prepareStatement(
1082
      "INSERT INTO xml_revisions " +
1083
        "(revisionid, docid, rootnodeid, docname, doctype, " +
1084
        "user_owner, user_updated, date_created, date_updated, " +
1085
        "server_location, rev, public_access, catalog_id) " +
1086
      "SELECT null, ?, rootnodeid, docname, doctype, " + 
1087
        "user_owner, ?, sysdate, sysdate, server_location, rev, " +
1088
        "public_access, catalog_id " +
1089
      "FROM xml_documents " +
1090
      "WHERE docid = ?");
1091
    // Bind the values to the query and execute it
1092
    pstmt.setString(1, docid);
1093
    pstmt.setString(2, user);
1094
    pstmt.setString(3, docid);
1095
    pstmt.execute();
1096
    pstmt.close();
1097

    
1098
  }
1099

    
1100
  /**
1101
   * the main routine used to test the DBWriter utility.
1102
   * <p>
1103
   * Usage: java DocumentImpl <-f filename -a action -d docid>
1104
   *
1105
   * @param filename the filename to be loaded into the database
1106
   * @param action the action to perform (READ, INSERT, UPDATE, DELETE)
1107
   * @param docid the id of the document to process
1108
   */
1109
  static public void main(String[] args) {
1110
     
1111
    try {
1112
      String filename    = null;
1113
      String dtdfilename = null;
1114
      String action      = null;
1115
      String docid       = null;
1116
      boolean showRuntime = false;
1117
      boolean useOldReadAlgorithm = false;
1118

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

    
1161
      // Print usage message if the arguments are not valid
1162
      if (!argsAreValid) {
1163
        System.err.println("Wrong number of arguments!!!");
1164
        System.err.println(
1165
          "USAGE: java DocumentImpl [-t] <-a INSERT> [-d docid] <-f filename> "+
1166
          "[-r dtdfilename]");
1167
        System.err.println(
1168
          "   OR: java DocumentImpl [-t] <-a UPDATE -d docid -f filename> " +
1169
          "[-r dtdfilename]");
1170
        System.err.println(
1171
          "   OR: java DocumentImpl [-t] <-a DELETE -d docid>");
1172
        System.err.println(
1173
          "   OR: java DocumentImpl [-t] [-old] <-a READ -d docid>");
1174
        return;
1175
      }
1176
      
1177
      // Time the request if asked for
1178
      double startTime = System.currentTimeMillis();
1179
      
1180
      // Open a connection to the database
1181
      MetaCatUtil util = new MetaCatUtil();
1182
      Connection dbconn = util.openDBConnection();
1183

    
1184
      double connTime = System.currentTimeMillis();
1185
      // Execute the action requested (READ, INSERT, UPDATE, DELETE)
1186
      if (action.equals("READ")) {
1187
          DocumentImpl xmldoc = new DocumentImpl( dbconn, docid );
1188
          if (useOldReadAlgorithm) {
1189
            System.out.println(xmldoc.readUsingSlowAlgorithm());
1190
          } else {
1191
            xmldoc.toXml(new PrintWriter(System.out));
1192
          }
1193
      } else if (action.equals("DELETE")) {
1194
        DocumentImpl.delete(dbconn, docid, null, null);
1195
        System.out.println("Document deleted: " + docid);
1196
      } else {
1197
        String newdocid = DocumentImpl.write(dbconn, filename, null,
1198
                                             dtdfilename, action, docid,
1199
                                             null, null);
1200
        if ((docid != null) && (!docid.equals(newdocid))) {
1201
          if (action.equals("INSERT")) {
1202
            System.out.println("New document ID generated!!! ");
1203
          } else if (action.equals("UPDATE")) {
1204
            System.out.println("ERROR: Couldn't update document!!! ");
1205
          }
1206
        } else if ((docid == null) && (action.equals("UPDATE"))) {
1207
          System.out.println("ERROR: Couldn't update document!!! ");
1208
        }
1209
        System.out.println("Document processing finished for: " + filename
1210
              + " (" + newdocid + ")");
1211
      }
1212

    
1213
      double stopTime = System.currentTimeMillis();
1214
      double dbOpenTime = (connTime - startTime)/1000;
1215
      double insertTime = (stopTime - connTime)/1000;
1216
      double executionTime = (stopTime - startTime)/1000;
1217
      if (showRuntime) {
1218
        System.out.println("\n\nTotal Execution time was: " + 
1219
                           executionTime + " seconds.");
1220
        System.out.println("Time to open DB connection was: " + dbOpenTime + 
1221
                           " seconds.");
1222
        System.out.println("Time to insert document was: " + insertTime +
1223
                           " seconds.");
1224
      }
1225
      dbconn.close();
1226
    } catch (McdbException me) {
1227
      me.toXml(new PrintWriter(System.err));
1228
    } catch (AccessionNumberException ane) {
1229
      System.out.println("ERROR: Couldn't delete document!!! ");
1230
      System.out.println(ane.getMessage());
1231
    } catch (Exception e) {
1232
      System.err.println("EXCEPTION HANDLING REQUIRED");
1233
      System.err.println(e.getMessage());
1234
      e.printStackTrace(System.err);
1235
    }
1236
  }
1237
}
(26-26/43)