Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that represents an XML document
4
 *  Copyright: 2000 Regents of the University of California and the
5
 *             National Center for Ecological Analysis and Synthesis
6
 *    Authors: Matt Jones
7
 *    Release: @release@
8
 *
9
 *   '$Author: berkley $'
10
 *     '$Date: 2001-01-18 15:15:21 -0800 (Thu, 18 Jan 2001) $'
11
 * '$Revision: 675 $'
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
  private String doctitle = null;
74
  private String createdate = null;
75
  private String updatedate = null;
76
  private String system_id = null;
77
  private String userowner = null;
78
  private String userupdated = null;
79
  private int rev;
80
  private int serverlocation;
81
  private int publicaccess; 
82
  private long rootnodeid;
83
  private ElementNode rootNode = null;
84
  private TreeSet nodeRecordList = null;
85

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

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

    
162
  /**
163
   * get the document name
164
   */
165
  public String getDocname() {
166
    return docname;
167
  }
168

    
169
  /**
170
   * get the document type (which is the PublicID)
171
   */
172
  public String getDoctype() {
173
    return doctype;
174
  }
175

    
176
  /**
177
   * get the system identifier
178
   */
179
  public String getSystemID() {
180
    return system_id;
181
  }
182

    
183
  /**
184
   * get the root node identifier
185
   */
186
  public long getRootNodeID() {
187
    return rootnodeid;
188
  }
189
  
190
  /**
191
   * get the creation date
192
   */
193
  public String getCreateDate() {
194
    return createdate;
195
  }
196
  
197
  /**
198
   * get the update date
199
   */
200
  public String getUpdateDate() {
201
    return updatedate;
202
  }
203

    
204
  /** 
205
   * Get the document identifier (docid)
206
   */
207
  public String getDocID() {
208
    return docid;
209
  }
210
  
211
  /**
212
   *get the document title
213
   */
214
  public String getDocTitle() {
215
    return doctitle;
216
  }
217
  
218
  public String getUserowner() {
219
    return userowner;
220
  }
221
  
222
  public String getUserupdated() {
223
    return userupdated;
224
  }
225
  
226
  public int getServerlocation() {
227
    return serverlocation;
228
  }
229
  
230
  public int getPublicaccess() {
231
    return publicaccess;
232
  }
233
  
234
  public int getRev() {
235
    return rev;
236
  }
237

    
238
  /**
239
   * Print a string representation of the XML document
240
   */
241
  public String toString()
242
  {
243
    StringWriter docwriter = new StringWriter();
244
    this.toXml(docwriter);
245
    String document = docwriter.toString();
246
    return document;
247
  }
248

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

    
263
    // Create the elements from the downloaded data in the TreeSet
264
    rootNode = new ElementNode(nodeRecordList, rootnodeid);
265

    
266
    // Append the resulting document to the StringBuffer and return it
267
    doc.append("<?xml version=\"1.0\"?>\n");
268
      
269
    if (docname != null) {
270
      if ((doctype != null) && (system_id != null)) {
271
        doc.append("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
272
                   "\" \"" + system_id + "\">\n");
273
      } else {
274
        doc.append("<!DOCTYPE " + docname + ">\n");
275
      }
276
    }
277
    doc.append(rootNode.toString());
278
  
279
    return (doc.toString());
280
  }
281

    
282
  /**
283
   * Print a text representation of the XML document to a Writer
284
   *
285
   * @param pw the Writer to which we print the document
286
   */
287
  public void toXml(Writer pw)
288
  {
289
    PrintWriter out = null;
290
    if (pw instanceof PrintWriter) {
291
      out = (PrintWriter)pw;
292
    } else {
293
      out = new PrintWriter(pw);
294
    }
295

    
296
    MetaCatUtil util = new MetaCatUtil();
297
    
298
    Stack openElements = new Stack();
299
    boolean atRootElement = true;
300
    boolean previousNodeWasElement = false;
301

    
302
    // Step through all of the node records we were given
303
    Iterator it = nodeRecordList.iterator();
304
    while (it.hasNext()) {
305
      NodeRecord currentNode = (NodeRecord)it.next();
306
      //util.debugMessage("[Got Node ID: " + currentNode.nodeid +
307
                          //" (" + currentNode.parentnodeid +
308
                          //", " + currentNode.nodeindex + 
309
                          //", " + currentNode.nodetype + 
310
                          //", " + currentNode.nodename + 
311
                          //", " + currentNode.nodedata + ")]");
312

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

    
342
      // Handle the DOCUMENT node
343
      if (currentNode.nodetype.equals("DOCUMENT")) {
344
        out.println("<?xml version=\"1.0\"?>");
345
      
346
        if (docname != null) {
347
          if ((doctype != null) && (system_id != null)) {
348
            out.println("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
349
                       "\" \"" + system_id + "\">");
350
          } else {
351
            out.println("<!DOCTYPE " + docname + ">");
352
          }
353
        }
354

    
355
      // Handle the ELEMENT nodes
356
      } else if (currentNode.nodetype.equals("ELEMENT")) {
357
        if (atRootElement) {
358
          atRootElement = false;
359
        } else {
360
          if (previousNodeWasElement) {
361
            out.print(">");
362
          }
363
        }
364
        openElements.push(currentNode);
365
        util.debugMessage("\n PUSHED: " + currentNode.nodename);
366
        previousNodeWasElement = true;
367
        out.print("<" + currentNode.nodename);
368

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

    
380
      // Handle the COMMENT nodes
381
      } else if (currentNode.nodetype.equals("COMMENT")) {
382
        if (previousNodeWasElement) {
383
          out.print(">");
384
        }
385
        out.print("<!--" + currentNode.nodedata + "-->");
386
        previousNodeWasElement = false;
387

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

    
397
      // Handle any other node type (do nothing)
398
      } else {
399
        // Any other types of nodes are not handled.
400
        // Probably should throw an exception here to indicate this
401
      }
402
      out.flush();
403
    }
404

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

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

    
503
      pstmt.execute();
504
      ResultSet rs = pstmt.getResultSet();
505
      boolean tableHasRows = rs.next();
506
      if (tableHasRows) {
507
        this.docname        = rs.getString(1);
508
        this.doctype        = rs.getString(2);
509
        this.rootnodeid     = rs.getLong(3);
510
        this.doctitle       = rs.getString(4);
511
        this.createdate     = rs.getString(5);
512
        this.updatedate     = rs.getString(6);
513
        this.userowner      = rs.getString(7);
514
        this.userupdated    = rs.getString(8);
515
        this.serverlocation = rs.getInt(9);
516
        //this.publicaccess   = rs.getInt(10);
517
        this.rev            = rs.getInt(10);
518
      } 
519

    
520
      if (this.doctype != null) {
521
        pstmt =
522
          conn.prepareStatement("SELECT system_id " +
523
                                  "FROM xml_catalog " +
524
                                 "WHERE public_id LIKE ?");
525
        // Bind the values to the query
526
        pstmt.setString(1, doctype);
527
  
528
        pstmt.execute();
529
        rs = pstmt.getResultSet();
530
        tableHasRows = rs.next();
531
        if (tableHasRows) {
532
          this.system_id  = rs.getString(1);
533
        } 
534
        //pstmt.close();
535
      }
536
    } catch (SQLException e) {
537
      System.out.println("error in DocumentImpl.getDocumentInfo: " + 
538
                          e.getMessage());
539
      e.printStackTrace(System.out);
540
      throw new McdbException("Error accessing database connection in " +
541
                              "DocumentImpl.getDocumentInfo: ", e);
542
    }
543

    
544
    if (this.docname == null) {
545
      throw new McdbDocNotFoundException("Document not found: " + docid);
546
    }
547
  }
548

    
549
  /**
550
   * Look up the node data from the database
551
   *
552
   * @param rootnodeid the id of the root node of the node tree to look up
553
   */
554
  private TreeSet getNodeRecordList(long rootnodeid) throws McdbException 
555
  {
556
    PreparedStatement pstmt;
557
    TreeSet nodeRecordList = new TreeSet(new NodeComparator());
558
    long nodeid = 0;
559
    long parentnodeid = 0;
560
    long nodeindex = 0;
561
    String nodetype = null;
562
    String nodename = null;
563
    String nodedata = null;
564

    
565
    try {
566
      pstmt =
567
      conn.prepareStatement("SELECT nodeid,parentnodeid,nodeindex, " +
568
           "nodetype,nodename,"+               
569
           "replace(" +
570
           "replace(" +
571
           "replace(nodedata,'&','&amp;') " +
572
           ",'<','&lt;') " +
573
           ",'>','&gt;') " +
574
           "FROM xml_nodes WHERE rootnodeid = ?");
575

    
576
      // Bind the values to the query
577
      pstmt.setLong(1, rootnodeid);
578

    
579
      pstmt.execute();
580
      ResultSet rs = pstmt.getResultSet();
581
      boolean tableHasRows = rs.next();
582
      while (tableHasRows) {
583
        nodeid = rs.getLong(1);
584
        parentnodeid = rs.getLong(2);
585
        nodeindex = rs.getLong(3);
586
        nodetype = rs.getString(4);
587
        nodename = rs.getString(5);
588
        nodedata = rs.getString(6);
589

    
590
        // add the data to the node record list hashtable
591
        NodeRecord currentRecord = new NodeRecord(nodeid, parentnodeid, 
592
                                   nodeindex, nodetype, nodename, nodedata);
593
        nodeRecordList.add(currentRecord);
594

    
595
        // Advance to the next node
596
        tableHasRows = rs.next();
597
      } 
598
      pstmt.close();
599

    
600
    } catch (SQLException e) {
601
      throw new McdbException("Error accessing database connection from " +
602
                              "DocumentImpl.getNodeRecordList ", e);
603
    }
604

    
605
    if (nodeRecordList != null) {
606
      return nodeRecordList;
607
    } else {
608
      throw new McdbException("Error getting node data: " + docid);
609
    }
610
  }
611
  
612
  /** creates SQL code and inserts new document into DB connection 
613
   default serverCode of 1*/
614
  private void writeDocumentToDB(String action, String user)
615
               throws SQLException, Exception
616
  {
617
    writeDocumentToDB(action, user, 1);
618
  }
619

    
620
 /** creates SQL code and inserts new document into DB connection */
621
  private void writeDocumentToDB(String action, String user, int serverCode) 
622
               throws SQLException, Exception {
623
    try {
624
      PreparedStatement pstmt = null;
625

    
626
      if (action.equals("INSERT")) {
627
        //AccessionNumber ac = new AccessionNumber();
628
        //this.docid = ac.generate(docid, "INSERT");
629
        pstmt = conn.prepareStatement(
630
            "INSERT INTO xml_documents " +
631
            "(docid, rootnodeid, docname, doctype, user_owner, " +
632
            "user_updated, date_created, date_updated, server_location) " +
633
            "VALUES (?, ?, ?, ?, ?, ?, sysdate, sysdate, ?)");
634
        //note that the server_location is set to 1. 
635
        //this means that "localhost" in the xml_replication table must
636
        //always be the first entry!!!!!
637
        
638
        // Bind the values to the query
639
        pstmt.setString(1, this.docid);
640
        pstmt.setLong(2, rootnodeid);
641
        pstmt.setString(3, docname);
642
        pstmt.setString(4, doctype);
643
        pstmt.setString(5, user);
644
        pstmt.setString(6, user);
645
        pstmt.setInt(7, serverCode);
646
      } else if (action.equals("UPDATE")) {
647

    
648
        // Save the old document entry in a backup table
649
        DocumentImpl.archiveDocRevision( conn, docid, user );
650
        DocumentImpl thisdoc = new DocumentImpl(conn, docid);
651
        int thisrev = thisdoc.getRev();
652
        thisrev++;
653
        // Delete index for the old version of docid
654
        // The new index is inserting on the next calls to DBSAXNode
655
        pstmt = conn.prepareStatement(
656
                "DELETE FROM xml_index WHERE docid='" + this.docid + "'");
657
        pstmt.execute();
658
        //pstmt.close();
659

    
660
        // Update the new document to reflect the new node tree
661
        pstmt = conn.prepareStatement(
662
            "UPDATE xml_documents " +
663
            "SET rootnodeid = ?, docname = ?, doctype = ?, " +
664
            "user_updated = ?, date_updated = sysdate, " +
665
            "server_location = ?, rev = ? WHERE docid LIKE ?");
666
        // Bind the values to the query
667
        pstmt.setLong(1, rootnodeid);
668
        pstmt.setString(2, docname);
669
        pstmt.setString(3, doctype);
670
        pstmt.setString(4, user);
671
        pstmt.setInt(5, serverCode);
672
        pstmt.setInt(6, thisrev);
673
        pstmt.setString(7, this.docid);
674

    
675
      } else {
676
        System.err.println("Action not supported: " + action);
677
      }
678

    
679
      // Do the insertion
680
      pstmt.execute();
681
      pstmt.close();
682

    
683
    } catch (SQLException sqle) {
684
      throw sqle;
685
    } catch (Exception e) {
686
      throw e;
687
    }
688
  }
689

    
690
  /**
691
   * Get the document title
692
   */
693
  public String getTitle() {
694
    return doctitle;
695
  }
696

    
697
  /**
698
   * Set the document title
699
   *
700
   * @param title the new title for the document
701
   */
702

    
703
  public void setTitle( String title ) {
704
    this.doctitle = title;
705
    try {
706
      PreparedStatement pstmt;
707
      pstmt = conn.prepareStatement(
708
            "UPDATE xml_documents " +
709
            " SET doctitle = ? " +
710
            "WHERE docid = ?");
711

    
712
      // Bind the values to the query
713
      pstmt.setString(1, doctitle);
714
      pstmt.setString(2, docid);
715

    
716
      // Do the insertion
717
      pstmt.execute();
718
      pstmt.close();
719
    } catch (SQLException e) {
720
      System.out.println("Error in DocumentImpl.setTitle: " + e.getMessage());
721
    }
722
  }
723

    
724
  /**
725
   * Write an XML file to the database, given a filename
726
   *
727
   * @param conn the JDBC connection to the database
728
   * @param filename the filename to be loaded into the database
729
   * @param action the action to be performed (INSERT OR UPDATE)
730
   * @param docid the docid to use for the INSERT OR UPDATE
731
   */
732
  public static String write(Connection conn,String filename,
733
                             String aclfilename,String dtdfilename,
734
                             String action, String docid, String user,
735
                             String group )
736
                throws Exception {
737
                  
738
    Reader acl = null;
739
    if ( aclfilename != null ) {
740
      acl = new FileReader(new File(aclfilename).toString());
741
    }
742
    Reader dtd = null;
743
    if ( dtdfilename != null ) {
744
      dtd = new FileReader(new File(dtdfilename).toString());
745
    }
746
    return write ( conn, new FileReader(new File(filename).toString()),
747
                   acl, dtd, action, docid, user, group);
748
  }
749

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

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

    
816
  public static String write( Connection conn,Reader xml,Reader acl,Reader dtd,
817
                              String action, String docid, String user,
818
                              String group, int serverCode, boolean override)
819
                throws Exception
820
  {
821
    //MOVED DOWN IN RelationHandler obj before write of relations
822
    //if(action.equals("UPDATE"))
823
    //{
824
    //  RelationHandler.deleteRelations(docid);
825
    //}
826
    
827
    int updaterev;
828
    MetaCatUtil util = new MetaCatUtil();
829
        // Determine if the docid is OK for INSERT or UPDATE
830
    AccessionNumber ac = new AccessionNumber(conn);
831
    String newdocid = ac.generate(docid, action);
832
    
833
    MetaCatUtil.debugMessage("action: " + action + " servercode: " + 
834
                             serverCode + " override: " + override);
835
                        
836
    if((serverCode != 1 && action.equals("UPDATE")) && !override)
837
    { //if this document being written is not a resident of this server then
838
      //we need to try to get a lock from it's resident server.  If the
839
      //resident server will not give a lock then we send the user a message
840
      //saying that he/she needs to download a new copy of the file and
841
      //merge the differences manually.
842
      int istreamInt; 
843
      char istreamChar;
844
      DocumentImpl newdoc = new DocumentImpl(conn, docid);
845
      updaterev = newdoc.getRev();
846
      String server = MetacatReplication.getServer(serverCode);
847
      MetacatReplication.replLog("attempting to lock " + docid);
848
      URL u = new URL("http://" + server + "?action=getlock&updaterev=" + 
849
                      updaterev + "&docid=" + docid);
850
      System.out.println("sending message: " + u.toString());
851
      String serverResStr = MetacatReplication.getURLContent(u);
852
      String openingtag = serverResStr.substring(0, serverResStr.indexOf(">")+1);
853
      
854
      if(openingtag.equals("<lockgranted>"))
855
      {//the lock was granted go ahead with the insert
856
        try 
857
        {
858
          MetacatReplication.replLog("lock granted for " + docid + " from " +
859
                                      server);
860
          XMLReader parser = initializeParser(conn, action, newdocid, 
861
                                              user, group, serverCode, dtd);
862
          conn.setAutoCommit(false);
863
          parser.parse(new InputSource(xml));
864
          conn.commit();
865
          conn.setAutoCommit(true);
866
        } 
867
        catch (Exception e) 
868
        {
869
          conn.rollback();
870
          conn.setAutoCommit(true);
871
          throw e;
872
        }
873
                
874
        //after inserting the document locally, tell the document's home server
875
        //to come get a copy from here.
876
        ForceReplicationHandler frh = new ForceReplicationHandler(docid);
877
        
878
        if ( (docid != null) && !(newdocid.equals(docid)) ) 
879
        {
880
          return new String("New document ID generated:" + newdocid);
881
        } 
882
        else 
883
        {
884
          return newdocid;
885
        }
886
      }
887
      else if(openingtag.equals("<filelocked>"))
888
      {//the file is currently locked by another user
889
       //notify our user to wait a few minutes, check out a new copy and try
890
       //again.
891
        //System.out.println("file locked");
892
        MetacatReplication.replLog("lock denied for " + docid + " on " +
893
                                   server + " reason: file already locked");
894
        throw new Exception("The file specified is already locked by another " +
895
                            "user.  Please wait 30 seconds, checkout the " +
896
                            "newer document, merge your changes and try " +
897
                            "again.");
898
      }
899
      else if(openingtag.equals("<outdatedfile>"))
900
      {//our file is outdated.  notify our user to check out a new copy of the
901
       //file and merge his version with the new version.
902
        //System.out.println("outdated file");
903
        MetacatReplication.replLog("lock denied for " + docid + " on " +
904
                                    server + " reason: local file outdated");
905
        throw new Exception("The file you are trying to update is an outdated" +
906
                            " version.  Please checkout the newest document, " +
907
                            "merge your changes and try again.");
908
      }
909
    }
910
    
911
    if ( action.equals("UPDATE") ) {
912
      // check for 'write' permission for 'user' to update this document
913

    
914
      if ( !hasPermission(conn, user, group, docid) ) {
915
        throw new Exception("User " + user + 
916
              " does not have permission to update XML Document #" + docid);
917
      }          
918
    }
919

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

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

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

    
975
    conn.setAutoCommit(false);
976
    // Copy the record to the xml_revisions table
977
    DocumentImpl.archiveDocRevision( conn, docid, user );
978

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

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

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

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

    
1041
      // Get an instance of the parser
1042
      MetaCatUtil util = new MetaCatUtil();
1043
      String parserName = util.getOption("saxparser");
1044
      parser = XMLReaderFactory.createXMLReader(parserName);
1045

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

    
1064
    } catch (Exception e) {
1065
      throw e;
1066
    }
1067

    
1068
    return parser;
1069
  }
1070

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

    
1093
  }
1094

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

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

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

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

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