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: tao $'
10
 *     '$Date: 2002-04-30 08:18:25 -0700 (Tue, 30 Apr 2002) $'
11
 * '$Revision: 1039 $'
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
import java.io.*;
40
import java.text.SimpleDateFormat;
41

    
42
import java.util.Date;
43
import java.util.Iterator;
44
import java.util.Stack;
45
import java.util.TreeSet;
46
import java.util.Enumeration;
47

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

    
59
import java.net.URL;
60

    
61
import edu.ucsb.nceas.dbadapter.AbstractDatabase;
62

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

    
70
  static final int ALL = 1;
71
  static final int WRITE = 2;
72
  static final int READ = 4;
73
  private static final AbstractDatabase dbAdapter = MetaCatUtil.dbAdapter;
74

    
75
  private Connection conn = null;
76
  private String docid = null;
77
  private String updatedVersion=null;
78
  private String docname = null;
79
  private String doctype = null;
80
// DOCTITLE attr cleared from the db
81
//  private String doctitle = null;
82
  private String createdate = null;
83
  private String updatedate = null;
84
  private String system_id = null;
85
  private String userowner = null;
86
  private String userupdated = null;
87
  private int rev;
88
  private int serverlocation;
89
  private String publicaccess; 
90
  private long rootnodeid;
91
  private ElementNode rootNode = null;
92
  private TreeSet nodeRecordList = null;
93
  
94
  /**
95
   * Constructor used to create a document and read the document information
96
   * from the database.  If readNodes is false, then the node data is not 
97
   * read at this time, but is deferred until it is needed (such as when a 
98
   * call to toXml() is made).  
99
   *
100
   * @param conn the database connection from which to read the document
101
   * @param docid the identifier of the document to be created
102
   * @param readNodes flag indicating whether the xmlnodes should be read
103
   */
104
  public DocumentImpl(Connection conn, String docid, boolean readNodes) 
105
         throws McdbException 
106
  {
107
    try {
108
      this.conn = conn;
109
      this.docid = docid;
110
      
111
      // Look up the document information
112
      getDocumentInfo(docid);
113

    
114
      if (readNodes) {
115
        // Download all of the document nodes using a single SQL query
116
        // The sort order of the records is determined by the NodeComparator
117
        // class, and needs to represent a depth-first traversal for the
118
        // toXml() method to work properly
119
        nodeRecordList = getNodeRecordList(rootnodeid);
120
      }
121
    
122
    } catch (McdbException ex) {
123
      throw ex;
124
    } catch (Throwable t) {
125
      throw new McdbException("Error reading document from " +
126
                              "DocumentImpl.DocumentImpl: " + docid);
127
    }
128
  }
129

    
130
  /**
131
   * Constructor, creates document from database connection, used 
132
   * for reading the document
133
   *
134
   * @param conn the database connection from which to read the document
135
   * @param docid the identifier of the document to be created
136
   */
137
  public DocumentImpl(Connection conn, String docid) throws McdbException 
138
  {
139
    this(conn, docid, true);
140
  }
141

    
142
  /** 
143
   * Construct a new document instance, writing the contents to the database.
144
   * This method is called from DBSAXHandler because we need to know the
145
   * root element name for documents without a DOCTYPE before creating it.
146
   *
147
   * @param conn the JDBC Connection to which all information is written
148
   * @param rootnodeid - sequence id of the root node in the document
149
   * @param docname - the name of DTD, i.e. the name immediately following 
150
   *        the DOCTYPE keyword ( should be the root element name ) or
151
   *        the root element name if no DOCTYPE declaration provided
152
   *        (Oracle's and IBM parsers are not aware if it is not the 
153
   *        root element name)
154
   * @param doctype - Public ID of the DTD, i.e. the name immediately 
155
   *                  following the PUBLIC keyword in DOCTYPE declaration or
156
   *                  the docname if no Public ID provided or
157
   *                  null if no DOCTYPE declaration provided
158
   * @param docid the docid to use for the INSERT OR UPDATE
159
   * @param action the action to be performed (INSERT OR UPDATE)
160
   * @param user the user that owns the document
161
   * @param pub flag for public "read" access on document
162
   * @param serverCode the serverid from xml_replication on which this document
163
   *        resides.
164
   *
165
   */
166
  public DocumentImpl(Connection conn, long rootnodeid, String docname, 
167
                      String doctype, String docid, String action, String user,
168
                      String pub, String catalogid, int serverCode)
169
                      throws SQLException, Exception
170
  {
171
    this.conn = conn;
172
    this.rootnodeid = rootnodeid;
173
    this.docname = docname;
174
    this.doctype = doctype;
175
    this.docid = docid;
176
    writeDocumentToDB(action, user, pub, catalogid, serverCode);
177
  }
178
  
179
  /** 
180
   * Construct a new document instance, writing the contents to the database.
181
   * This method is called from DBSAXHandler because we need to know the
182
   * root element name for documents without a DOCTYPE before creating it.
183
   *
184
   * In this constructor, the docid is without rev. There is a string rev to 
185
   * specify the revision user want to upadate. The revion is only need to be
186
   * greater than current one. It is not need to be sequent number just after
187
   * current one. So it is only used in update action
188
   * @param conn the JDBC Connection to which all information is written
189
   * @param rootnodeid - sequence id of the root node in the document
190
   * @param docname - the name of DTD, i.e. the name immediately following 
191
   *        the DOCTYPE keyword ( should be the root element name ) or
192
   *        the root element name if no DOCTYPE declaration provided
193
   *        (Oracle's and IBM parsers are not aware if it is not the 
194
   *        root element name)
195
   * @param doctype - Public ID of the DTD, i.e. the name immediately 
196
   *                  following the PUBLIC keyword in DOCTYPE declaration or
197
   *                  the docname if no Public ID provided or
198
   *                  null if no DOCTYPE declaration provided
199
   * @param docid the docid to use for the UPDATE, no version number
200
   * @param version, need to be update
201
   * @param action the action to be performed (INSERT OR UPDATE)
202
   * @param user the user that owns the document
203
   * @param pub flag for public "read" access on document
204
   * @param serverCode the serverid from xml_replication on which this document
205
   *        resides.
206
   *
207
   */
208
  public DocumentImpl(Connection conn, long rootNodeId, String docName, 
209
                      String docType, String docId, String newRevision, 
210
                      String action, String user,
211
                      String pub, String catalogId, int serverCode)
212
                      throws SQLException, Exception
213
  {
214
    this.conn = conn;
215
    this.rootnodeid = rootNodeId;
216
    this.docname = docName;
217
    this.doctype = docType;
218
    this.docid = docId;
219
    this.updatedVersion = newRevision;
220
    writeDocumentToDB(action, user, pub, catalogId, serverCode);
221
  }
222
  
223
  /**
224
   * This method will be call in handleUploadRequest in MetacatServlet class
225
   */
226
  public static void registerDocument(
227
                     String docname, String doctype, String accnum, String user)
228
                     throws SQLException, AccessionNumberException, Exception
229
  {
230
    Connection dbConn = null;
231
    MetaCatUtil ut = new MetaCatUtil();
232
    dbConn = ut.getConnection();
233
    // get server location for this doc
234
    int serverLocation=getServerLocationNumber(dbConn,accnum);
235
    ut.returnConnection(dbConn);
236
    registerDocument(docname, doctype,accnum, user, serverLocation);
237
    
238
  }
239
  /**
240
   * Register a document that resides on the filesystem with the database.
241
   * (ie, just an entry in xml_documents, nothing in xml_nodes).
242
   * Creates a reference to a filesystem document (used for non-xml data files).
243
   *
244
   * @param conn the JDBC Connection to which all information is written
245
   * @param docname - the name of DTD, i.e. the name immediately following 
246
   *        the DOCTYPE keyword ( should be the root element name ) or
247
   *        the root element name if no DOCTYPE declaration provided
248
   *        (Oracle's and IBM parsers are not aware if it is not the 
249
   *        root element name)
250
   * @param doctype - Public ID of the DTD, i.e. the name immediately 
251
   *                  following the PUBLIC keyword in DOCTYPE declaration or
252
   *                  the docname if no Public ID provided or
253
   *                  null if no DOCTYPE declaration provided
254
   * @param accnum the accession number to use for the INSERT OR UPDATE, which 
255
   *               includes a revision number for this revision of the document 
256
   *               (e.g., knb.1.1)
257
   * @param user the user that owns the document
258
   * @param serverCode the serverid from xml_replication on which this document
259
   *        resides.
260
   */
261
  public static void registerDocument(
262
                     String docname, String doctype, String accnum, 
263
                     String user, int serverCode)
264
                     throws SQLException, AccessionNumberException, Exception
265
  {
266
    Connection dbconn = null;
267
    MetaCatUtil util = new MetaCatUtil();
268
    AccessionNumber ac;
269
    try {
270
      dbconn = util.openDBConnection();
271
      String docIdWithoutRev=MetaCatUtil.getDocIdFromString(accnum);
272
      int userSpecifyRev=MetaCatUtil.getVersionFromString(accnum);
273
      int revInDataBase=getLatestRevisionNumber(dbconn, docIdWithoutRev);
274
      //revIndataBase=-1, there is no record in xml_documents table
275
      //the data file is a new one, inert it into table
276
      //user specified rev should be great than 0
277
      if (revInDataBase==-1 && userSpecifyRev>0 )
278
      {
279
        ac = new AccessionNumber(dbconn, accnum, "insert");
280
      }
281
      //rev is greater the last revsion number and revInDataBase isn't -1
282
      // it is a updated data file
283
      else if (userSpecifyRev>revInDataBase && revInDataBase>0)
284
      {
285
        
286
        //archive the old entry 
287
        archiveDocRevision(dbconn, docIdWithoutRev, user);
288
        //delete the old entry in xml_documents
289
        deleteXMLDocuments(dbconn, docIdWithoutRev);
290
        ac = new AccessionNumber(dbconn, accnum, "update");
291
      }
292
      //other situation
293
      else
294
      {
295
        
296
        throw new Exception("Revision number couldn't be "
297
                    +userSpecifyRev);
298
      }
299
      String docid = ac.getDocid();
300
      String rev = ac.getRev();
301
      SimpleDateFormat formatter = new SimpleDateFormat ("yy-MM-dd HH:mm:ss");
302
      Date localtime = new Date();
303
      String dateString = formatter.format(localtime);
304
  
305
      String sqlDateString = "to_date('" + dateString + 
306
                                          "', 'YY-MM-DD HH24:MI:SS')";
307
  
308
      StringBuffer sql = new StringBuffer();
309
      sql.append("insert into xml_documents (docid, docname, doctype, ");
310
      sql.append("user_owner, user_updated, server_location, rev,date_created");
311
      sql.append(", date_updated, public_access) values ('");
312
      sql.append(docid).append("','");
313
      sql.append(docname).append("','");
314
      sql.append(doctype).append("','");
315
      sql.append(user).append("','");
316
      sql.append(user).append("','");
317
      sql.append(serverCode).append("','");
318
      sql.append(rev).append("',");
319
      sql.append(sqlDateString).append(",");
320
      sql.append(sqlDateString).append(",");
321
      sql.append("'0')");
322
      PreparedStatement pstmt = dbconn.prepareStatement(sql.toString());
323
      pstmt.execute();
324
      pstmt.close();
325
      dbconn.close();
326
    } finally {
327
      util.returnConnection(dbconn);
328
    }    
329
  }
330
  
331
 /**
332
   * This method will register a data file entry in xml_documents and save a
333
   * data file input Stream into file system..
334
   *
335
   * @param  input, the input stream which contain the file content.
336
   * @param  , the input stream which contain the file content
337
   * @param docname - the name of DTD, for data file, it is a docid number.
338
   * @param doctype - "BIN" for data file
339
   * @param accnum the accession number to use for the INSERT OR UPDATE, which 
340
   *               includes a revision number for this revision of the document 
341
   *               (e.g., knb.1.1)
342
   * @param user the user that owns the document
343
   * @param serverCode the serverid from xml_replication on which this document
344
   *        resides.
345
   */
346
 public static void writeDataFile(InputStream input, String filePath, 
347
    String docname, String doctype, String accnum, String user, int serverCode)
348
                     throws SQLException, AccessionNumberException, Exception
349
 {
350
    if (filePath==null||filePath.equals(""))
351
    {
352
      throw new 
353
            Exception("Please specify the directory where file will be store");
354
    }
355
    if (accnum==null||accnum.equals(""))
356
    {
357
      throw new Exception("Please specify the stored file name");
358
    }
359
    //make sure user have file lock grant(local metacat means have it too)
360
    if (getDataFileLockGrant(accnum))
361
    {
362
      //register data file into xml_documents table
363
      registerDocument(docname, doctype, accnum, user, serverCode);
364
      //write inputstream into file system.
365
      File dataDirectory = new File(filePath);
366
      File newFile = new File(dataDirectory, accnum); 
367
       
368
      // create a buffered byte output stream
369
      // that uses a default-sized output buffer
370
      FileOutputStream fos = new FileOutputStream(newFile);
371
      BufferedOutputStream outPut = new BufferedOutputStream(fos);
372

    
373
      BufferedInputStream bis = null;
374
      bis = new BufferedInputStream(input);
375
      byte[] buf = new byte[4 * 1024]; // 4K buffer
376
      int b = bis.read(buf);
377
       
378
      while (b != -1) 
379
      {
380
        outPut.write(buf, 0, b);
381
        b = bis.read(buf);
382
      }
383
      bis.close();
384
	    outPut.close();
385
	    fos.close();
386
      //force replication to other server
387
      MetaCatUtil util = new MetaCatUtil();
388
      if ((util.getOption("replicationdata")).equals("on"))
389
      {
390
        Connection dbConn = util.getConnection();
391
        //this is local server, force replication to every server in its server
392
        //list
393
        if (serverCode==1)
394
        {
395
          
396
          ForceReplicationHandler frh = new ForceReplicationHandler(accnum,
397
                  "insert",false,ReplicationHandler.buildServerList(dbConn));
398
          util.returnConnection(dbConn);
399
        }//if
400
        else
401
        {
402
          //if it is super hub, replication to server list
403
          if ((util.getOption("hub")).equals("super"))
404
          {
405
            ForceReplicationHandler frh = new ForceReplicationHandler(accnum, 
406
                true, ReplicationHandler.buildServerList(dbConn));
407
            util.returnConnection(dbConn);
408
          }
409
          else
410
          {
411
            //only replicate to home host of the document
412
            String docId=util.getDocIdFromString(accnum);
413
            ForceReplicationHandler frh = new ForceReplicationHandler(accnum, 
414
                true, ReplicationHandler.getHomeServer(docId));
415
          }
416
        }
417
      }//if
418
    }//if
419
 }
420
  
421
 /**
422
   * unRegister a an XML file (data file) from the database (actually, 
423
   * just make it a revision in the xml_revisions table and delete it
424
   * from xml_documents table). 
425
   *
426
   * @param docid the ID of the document to be deleted from the database
427
   */
428
  public static void unRegisterDocument( Connection conn, String accnum,
429
                                 String user, String[] groups )
430
                throws Exception 
431
  {
432
    
433

    
434
    // NEW - WHEN CLIENT ALWAYS PROVIDE ACCESSION NUMBER INCLUDING REV IN IT
435
    AccessionNumber ac = new AccessionNumber(conn, accnum, "DELETE");
436
    String docid = ac.getDocid();
437
    String rev = ac.getRev();
438
    
439

    
440
    // check for 'write' permission for 'user' to delete this document
441
    if ( !hasPermission(conn, user, groups, docid) ) {
442
      throw new Exception("User " + user + 
443
              " does not have permission to delete XML Document #" + accnum);
444
    }
445

    
446
    conn.setAutoCommit(false);
447
    // Copy the record to the xml_revisions table
448
    DocumentImpl.archiveDocRevision( conn, docid, user );
449

    
450
    // Now delete it from the xml_documents table
451
    
452
    Statement stmt = conn.createStatement();
453
    //stmt.execute("DELETE FROM xml_access WHERE docid = '" + docid + "'");
454
    stmt.execute("DELETE FROM xml_documents WHERE docid = '" + docid + "'");
455
    stmt.close();
456
    conn.commit();
457
    conn.setAutoCommit(true);
458
   
459
    
460
  }
461
  
462
  public static boolean getDataFileLockGrant(String accnum) 
463
                                                  throws Exception
464
  { 
465
    Connection dbConn = null;
466
    MetaCatUtil ut= new MetaCatUtil();
467
    dbConn = ut.getConnection();
468
    int serverLocation=getServerLocationNumber(dbConn,accnum);
469
    ut.returnConnection(dbConn);
470
    return getDataFileLockGrant(accnum,serverLocation);
471
  }
472
    
473
  /**
474
   * The method will check if metacat can get data file lock grant
475
   * If server code is 1, it get.
476
   * If server code is not 1 but call replication getlock successfully,
477
   * it get
478
   * else, it didn't get
479
   * @param accnum, the ID of the document 
480
   * @param action, the action to the document
481
   * @param serverCode, the server location code
482
   */
483
  public static boolean getDataFileLockGrant(String accnum, int serverCode)
484
                                          throws Exception 
485
  {
486
    boolean flag=true;
487
    MetaCatUtil util = new MetaCatUtil();
488
    String docid = util.getDocIdFromString(accnum);
489
    int rev = util.getVersionFromString(accnum);
490
    
491
    if (serverCode == 1)
492
    {
493
      flag=true;
494
      return flag;
495
    }
496
    
497
    //if((serverCode != 1 && action.equals("UPDATE")) )
498
    if (serverCode != 1)
499
    { //if this document being written is not a resident of this server then
500
      //we need to try to get a lock from it's resident server.  If the
501
      //resident server will not give a lock then we send the user a message
502
      //saying that he/she needs to download a new copy of the file and
503
      //merge the differences manually.
504
      
505
      String server = MetacatReplication.getServer(serverCode);
506
      MetacatReplication.replLog("attempting to lock " + accnum);
507
      URL u = new URL("https://" + server + "?server="
508
           +util.getLocalReplicationServerName()+"&action=getlock&updaterev=" 
509
           +rev + "&docid=" + docid);
510
      //System.out.println("sending message: " + u.toString());
511
      String serverResStr = MetacatReplication.getURLContent(u);
512
      String openingtag =serverResStr.substring(0, serverResStr.indexOf(">")+1);
513
      if(openingtag.equals("<lockgranted>"))
514
      {
515
        //the lock was granted go ahead with the insert
516
        //System.out.println("In lockgranted");
517
        MetacatReplication.replLog("lock granted for " + accnum + " from " +
518
                                      server);
519
        flag=true;  
520
        return flag;
521
      }//if
522

    
523
      else if(openingtag.equals("<filelocked>"))
524
      {//the file is currently locked by another user
525
       //notify our user to wait a few minutes, check out a new copy and try
526
       //again.
527
        //System.out.println("file locked");
528
        MetacatReplication.replLog("lock denied for " + accnum + " on " +
529
                                   server + " reason: file already locked");
530
        throw new Exception("The file specified is already locked by another " +
531
                            "user.  Please wait 30 seconds, checkout the " +
532
                            "newer document, merge your changes and try " +
533
                            "again.");
534
      }
535
      else if(openingtag.equals("<outdatedfile>"))
536
      {//our file is outdated.  notify our user to check out a new copy of the
537
       //file and merge his version with the new version.
538
        //System.out.println("outdated file");
539
        MetacatReplication.replLog("lock denied for " + accnum + " on " +
540
                                    server + " reason: local file outdated");
541
        throw new Exception("The file you are trying to update is an outdated" +
542
                            " version.  Please checkout the newest document, " +
543
                            "merge your changes and try again.");
544
      }//else if
545
    }//if
546
    
547
   return flag;
548
   
549
  }//getDataFileLockGrant
550
  
551
  /**
552
   * get the document name
553
   */
554
  public String getDocname() {
555
    return docname;
556
  }
557

    
558
  /**
559
   * get the document type (which is the PublicID)
560
   */
561
  public String getDoctype() {
562
    return doctype;
563
  }
564

    
565
  /**
566
   * get the system identifier
567
   */
568
  public String getSystemID() {
569
    return system_id;
570
  }
571

    
572
  /**
573
   * get the root node identifier
574
   */
575
  public long getRootNodeID() {
576
    return rootnodeid;
577
  }
578
  
579
  /**
580
   * get the creation date
581
   */
582
  public String getCreateDate() {
583
    return createdate;
584
  }
585
  
586
  /**
587
   * get the update date
588
   */
589
  public String getUpdateDate() {
590
    return updatedate;
591
  }
592

    
593
  /** 
594
   * Get the document identifier (docid)
595
   */
596
  public String getDocID() {
597
    return docid;
598
  }
599
  
600
// DOCTITLE attr cleared from the db
601
//  /**
602
//   *get the document title
603
//   */
604
//  public String getDocTitle() {
605
//    return doctitle;
606
//  }
607
  
608
  public String getUserowner() {
609
    return userowner;
610
  }
611
  
612
  public String getUserupdated() {
613
    return userupdated;
614
  }
615
  
616
  public int getServerlocation() {
617
    return serverlocation;
618
  }
619
  
620
  public String getPublicaccess() {
621
    return publicaccess;
622
  }
623
  
624
  public int getRev() {
625
    return rev;
626
  }
627
  
628
   /**
629
   * Print a string representation of the XML document
630
   */
631
  public String toString()
632
  {
633
    StringWriter docwriter = new StringWriter();
634
    try {
635
      this.toXml(docwriter);
636
    } catch (McdbException mcdbe) {
637
      return null;
638
    }
639
    String document = docwriter.toString();
640
    return document;
641
  }
642

    
643
  /**
644
   * Get a text representation of the XML document as a string
645
   * This older algorithm uses a recursive tree of Objects to represent the
646
   * nodes of the tree.  Each object is passed the data for the document 
647
   * and searches all of the document data to find its children nodes and
648
   * recursively build.  Thus, because each node reads the whole document,
649
   * this algorithm is extremely slow for larger documents, and the time
650
   * to completion is O(N^N) wrt the number of nodes.  See toXml() for a
651
   * better algorithm.
652
   */
653
  public String readUsingSlowAlgorithm() throws McdbException
654
  {
655
    StringBuffer doc = new StringBuffer();
656

    
657
    // First, check that we have the needed node data, and get it if not
658
    if (nodeRecordList == null) {
659
      nodeRecordList = getNodeRecordList(rootnodeid);
660
    }
661

    
662
    // Create the elements from the downloaded data in the TreeSet
663
    rootNode = new ElementNode(nodeRecordList, rootnodeid);
664

    
665
    // Append the resulting document to the StringBuffer and return it
666
    doc.append("<?xml version=\"1.0\"?>\n");
667
      
668
    if (docname != null) {
669
      if ((doctype != null) && (system_id != null)) {
670
        doc.append("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
671
                   "\" \"" + system_id + "\">\n");
672
      } else {
673
        doc.append("<!DOCTYPE " + docname + ">\n");
674
      }
675
    }
676
    doc.append(rootNode.toString());
677
  
678
    return (doc.toString());
679
  }
680

    
681
  /**
682
   * Print a text representation of the XML document to a Writer
683
   *
684
   * @param pw the Writer to which we print the document
685
   */
686
  public void toXml(Writer pw) throws McdbException
687
  {
688
    PrintWriter out = null;
689
    if (pw instanceof PrintWriter) {
690
      out = (PrintWriter)pw;
691
    } else {
692
      out = new PrintWriter(pw);
693
    }
694

    
695
    MetaCatUtil util = new MetaCatUtil();
696
    
697
    // First, check that we have the needed node data, and get it if not
698
    if (nodeRecordList == null) {
699
      nodeRecordList = getNodeRecordList(rootnodeid);
700
    }
701

    
702
    Stack openElements = new Stack();
703
    boolean atRootElement = true;
704
    boolean previousNodeWasElement = false;
705

    
706
    // Step through all of the node records we were given
707
    Iterator it = nodeRecordList.iterator();
708
    while (it.hasNext()) {
709
      NodeRecord currentNode = (NodeRecord)it.next();
710
      //util.debugMessage("[Got Node ID: " + currentNode.nodeid +
711
                          //" (" + currentNode.parentnodeid +
712
                          //", " + currentNode.nodeindex + 
713
                          //", " + currentNode.nodetype + 
714
                          //", " + currentNode.nodename + 
715
                          //", " + currentNode.nodedata + ")]");
716
      // Print the end tag for the previous node if needed
717
      //
718
      // This is determined by inspecting the parent nodeid for the
719
      // currentNode.  If it is the same as the nodeid of the last element
720
      // that was pushed onto the stack, then we are still in that previous
721
      // parent element, and we do nothing.  However, if it differs, then we
722
      // have returned to a level above the previous parent, so we go into
723
      // a loop and pop off nodes and print out their end tags until we get
724
      // the node on the stack to match the currentNode parentnodeid
725
      //
726
      // So, this of course means that we rely on the list of elements
727
      // having been sorted in a depth first traversal of the nodes, which
728
      // is handled by the NodeComparator class used by the TreeSet
729
      if (!atRootElement) {
730
        NodeRecord currentElement = (NodeRecord)openElements.peek();
731
        if ( currentNode.parentnodeid != currentElement.nodeid ) {
732
          while ( currentNode.parentnodeid != currentElement.nodeid ) {
733
            currentElement = (NodeRecord)openElements.pop();
734
            util.debugMessage("\n POPPED: " + currentElement.nodename);
735
            if (previousNodeWasElement) {
736
              out.print(">");
737
              previousNodeWasElement = false;
738
            }  
739
            if ( currentElement.nodeprefix != null ) {
740
              out.print("</" + currentElement.nodeprefix + ":" + 
741
                        currentElement.nodename + ">" );
742
            } else {
743
              out.print("</" + currentElement.nodename + ">" );
744
            }
745
            currentElement = (NodeRecord)openElements.peek();
746
          }
747
        }
748
      }
749

    
750
      // Handle the DOCUMENT node
751
      if (currentNode.nodetype.equals("DOCUMENT")) {
752
        out.println("<?xml version=\"1.0\"?>");
753
      
754
        if (docname != null) {
755
          if ((doctype != null) && (system_id != null)) {
756
            out.println("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
757
                       "\" \"" + system_id + "\">");
758
          } else {
759
            out.println("<!DOCTYPE " + docname + ">");
760
          }
761
        }
762

    
763
      // Handle the ELEMENT nodes
764
      } else if (currentNode.nodetype.equals("ELEMENT")) {
765
        if (atRootElement) {
766
          atRootElement = false;
767
        } else {
768
          if (previousNodeWasElement) {
769
            out.print(">");
770
          }
771
        }
772
        openElements.push(currentNode);
773
        util.debugMessage("\n PUSHED: " + currentNode.nodename);
774
        previousNodeWasElement = true;
775
        if ( currentNode.nodeprefix != null ) {
776
          out.print("<" + currentNode.nodeprefix + ":" + currentNode.nodename);
777
        } else {
778
          out.print("<" + currentNode.nodename);
779
        }
780

    
781
      // Handle the ATTRIBUTE nodes
782
      } else if (currentNode.nodetype.equals("ATTRIBUTE")) {
783
        if ( currentNode.nodeprefix != null ) {
784
          out.print(" " + currentNode.nodeprefix + ":" + currentNode.nodename +
785
                    "=\"" + currentNode.nodedata + "\"");
786
        } else {
787
          out.print(" " + currentNode.nodename + "=\"" +
788
                    currentNode.nodedata + "\"");
789
        }
790

    
791
      // Handle the NAMESPACE nodes
792
      } else if (currentNode.nodetype.equals("NAMESPACE")) {
793
        out.print(" xmlns:" + currentNode.nodename + "=\""
794
                 + currentNode.nodedata + "\"");
795

    
796
      // Handle the TEXT nodes
797
      } else if (currentNode.nodetype.equals("TEXT")) {
798
        if (previousNodeWasElement) {
799
          out.print(">");
800
        }
801
        out.print(currentNode.nodedata);
802
        previousNodeWasElement = false;
803

    
804
      // Handle the COMMENT nodes
805
      } else if (currentNode.nodetype.equals("COMMENT")) {
806
        if (previousNodeWasElement) {
807
          out.print(">");
808
        }
809
        out.print("<!--" + currentNode.nodedata + "-->");
810
        previousNodeWasElement = false;
811

    
812
      // Handle the PI nodes
813
      } else if (currentNode.nodetype.equals("PI")) {
814
        if (previousNodeWasElement) {
815
          out.print(">");
816
        }
817
        out.print("<?" + currentNode.nodename + " " +
818
                        currentNode.nodedata + "?>");
819
        previousNodeWasElement = false;
820

    
821
      // Handle any other node type (do nothing)
822
      } else {
823
        // Any other types of nodes are not handled.
824
        // Probably should throw an exception here to indicate this
825
      }
826
      out.flush();
827
    }
828

    
829
    // Print the final end tag for the root element
830
    while(!openElements.empty())
831
    {
832
      NodeRecord currentElement = (NodeRecord)openElements.pop();
833
      util.debugMessage("\n POPPED: " + currentElement.nodename);
834
      if ( currentElement.nodeprefix != null ) {
835
        out.print("</" + currentElement.nodeprefix + ":" + 
836
                  currentElement.nodename + ">" );
837
      } else {
838
        out.print("</" + currentElement.nodename + ">" );
839
      }
840
    }
841
    out.flush();
842
  }
843
  
844
  private boolean isRevisionOnly(DocumentIdentifier docid) throws Exception
845
  {
846
    //System.out.println("inRevisionOnly");
847
    PreparedStatement pstmt;
848
    String rev = docid.getRev();
849
    String newid = docid.getIdentifier();
850
    pstmt = conn.prepareStatement("select rev from xml_documents " +
851
                                  "where docid like '" + newid + "'");
852
    pstmt.execute();
853
    ResultSet rs = pstmt.getResultSet();
854
    boolean tablehasrows = rs.next();
855
    if(rev.equals("newest") || rev.equals("all"))
856
    {
857
      return false;
858
    }
859
    
860
    if(tablehasrows)
861
    {
862
      int r = rs.getInt(1);
863
      pstmt.close();
864
      if(new Integer(rev).intValue() == r)
865
      { //the current revision in in xml_documents
866
        //System.out.println("returning false");
867
        return false;
868
      }
869
      else if(new Integer(rev).intValue() < r)
870
      { //the current revision is in xml_revisions.
871
        //System.out.println("returning true");
872
        return true;
873
      }
874
      else if(new Integer(rev).intValue() > r)
875
      { //error, rev cannot be greater than r
876
        throw new Exception("requested revision cannot be greater than " +
877
                            "the latest revision number.");
878
      }
879
    }
880
    throw new Exception("the requested docid '" + docid.toString() + 
881
                        "' does not exist");
882
  }
883

    
884
  private void getDocumentInfo(String docid) throws McdbException, 
885
                                                    AccessionNumberException
886
  {
887
    getDocumentInfo(new DocumentIdentifier(docid));
888
  }
889
  
890
  /**
891
   * Look up the document type information from the database
892
   *
893
   * @param docid the id of the document to look up
894
   */
895
  private void getDocumentInfo(DocumentIdentifier docid) throws McdbException 
896
  {
897
    PreparedStatement pstmt;
898
    String table = "xml_documents";
899
    
900
    try
901
    {
902
      if(isRevisionOnly(docid))
903
      { //pull the document from xml_revisions instead of from xml_documents;
904
        table = "xml_revisions";
905
      }
906
    }
907
    catch(Exception e)
908
    {
909
      System.out.println("error in DocumentImpl.getDocumentInfo: " + 
910
                          e.getMessage());
911
    }
912
    
913
    //deal with the key words here.
914
    
915
    if(docid.getRev().equals("all"))
916
    {
917
      
918
    }
919
    
920
    try {
921
      StringBuffer sql = new StringBuffer();
922
// DOCTITLE attr cleared from the db
923
//      sql.append("SELECT docname, doctype, rootnodeid, doctitle, ");
924
      sql.append("SELECT docname, doctype, rootnodeid, ");
925
      sql.append("date_created, date_updated, user_owner, user_updated, ");
926
      sql.append("server_location, public_access, rev");
927
      sql.append(" FROM ").append(table);
928
      sql.append(" WHERE docid LIKE '").append(docid.getIdentifier());
929
      sql.append("' and rev like '").append(docid.getRev()).append("'");
930
      //System.out.println(sql.toString());
931
      pstmt =
932
        conn.prepareStatement(sql.toString());
933
      // Bind the values to the query
934
      //pstmt.setString(1, docid.getIdentifier());
935
      //pstmt.setString(2, docid.getRev());
936

    
937
      pstmt.execute();
938
      ResultSet rs = pstmt.getResultSet();
939
      boolean tableHasRows = rs.next();
940
      if (tableHasRows) {
941
        this.docname        = rs.getString(1);
942
        this.doctype        = rs.getString(2);
943
        this.rootnodeid     = rs.getLong(3);
944
// DOCTITLE attr cleared from the db
945
//        this.doctitle       = rs.getString(4);
946
        this.createdate     = rs.getString(4);
947
        this.updatedate     = rs.getString(5);
948
        this.userowner      = rs.getString(6);
949
        this.userupdated    = rs.getString(7);
950
        this.serverlocation = rs.getInt(8);
951
        this.publicaccess   = rs.getString(9);
952
        this.rev            = rs.getInt(10);
953
      } 
954
      pstmt.close();
955
      if (this.doctype != null) {
956
        pstmt =
957
          conn.prepareStatement("SELECT system_id " +
958
                                  "FROM xml_catalog " +
959
                                 "WHERE public_id = ?");
960
        // Bind the values to the query
961
        pstmt.setString(1, doctype);
962
  
963
        pstmt.execute();
964
        rs = pstmt.getResultSet();
965
        tableHasRows = rs.next();
966
        if (tableHasRows) {
967
          this.system_id  = rs.getString(1);
968
        } 
969
        pstmt.close();
970
      }
971
    } catch (SQLException e) {
972
      System.out.println("error in DocumentImpl.getDocumentInfo: " + 
973
                          e.getMessage());
974
      e.printStackTrace(System.out);
975
      throw new McdbException("Error accessing database connection in " +
976
                              "DocumentImpl.getDocumentInfo: ", e);
977
    }
978

    
979
    if (this.docname == null) {
980
      throw new McdbDocNotFoundException("Document not found: " + docid);
981
    }
982
  }
983

    
984
  /**
985
   * Look up the node data from the database
986
   *
987
   * @param rootnodeid the id of the root node of the node tree to look up
988
   */
989
  private TreeSet getNodeRecordList(long rootnodeid) throws McdbException 
990
  {
991
    PreparedStatement pstmt;
992
    TreeSet nodeRecordList = new TreeSet(new NodeComparator());
993
    long nodeid = 0;
994
    long parentnodeid = 0;
995
    long nodeindex = 0;
996
    String nodetype = null;
997
    String nodename = null;
998
    String nodeprefix = null;
999
    String nodedata = null;
1000
    String quotechar = dbAdapter.getStringDelimiter();
1001

    
1002
    try {
1003
      pstmt =
1004
      conn.prepareStatement("SELECT nodeid,parentnodeid,nodeindex, " +
1005
           "nodetype,nodename,nodeprefix,nodedata " +               
1006
           "FROM xml_nodes WHERE rootnodeid = ?");
1007

    
1008
      // Bind the values to the query
1009
      pstmt.setLong(1, rootnodeid);
1010

    
1011
      pstmt.execute();
1012
      ResultSet rs = pstmt.getResultSet();
1013
      boolean tableHasRows = rs.next();
1014
      while (tableHasRows) {
1015
        nodeid = rs.getLong(1);
1016
        parentnodeid = rs.getLong(2);
1017
        nodeindex = rs.getLong(3);
1018
        nodetype = rs.getString(4);
1019
        nodename = rs.getString(5);
1020
        nodeprefix = rs.getString(6);
1021
        nodedata = rs.getString(7);
1022
        nodedata = MetaCatUtil.normalize(nodedata);
1023
        // add the data to the node record list hashtable
1024
        NodeRecord currentRecord = new NodeRecord(nodeid,parentnodeid,nodeindex,
1025
                                      nodetype, nodename, nodeprefix, nodedata);
1026
        nodeRecordList.add(currentRecord);
1027

    
1028
        // Advance to the next node
1029
        tableHasRows = rs.next();
1030
      } 
1031
      pstmt.close();
1032

    
1033
    } catch (SQLException e) {
1034
      throw new McdbException("Error in DocumentImpl.getNodeRecordList " +
1035
                              e.getMessage());
1036
    }
1037

    
1038
    if (nodeRecordList != null) {
1039
      return nodeRecordList;
1040
    } else {
1041
      throw new McdbException("Error getting node data: " + docid);
1042
    }
1043
  }
1044
  
1045
// NOT USED ANY MORE
1046
//  /** creates SQL code and inserts new document into DB connection 
1047
//   default serverCode of 1*/
1048
//  private void writeDocumentToDB(String action, String user)
1049
//               throws SQLException, Exception
1050
//  {
1051
//    writeDocumentToDB(action, user, null, 1);
1052
//  }
1053

    
1054
 /** creates SQL code and inserts new document into DB connection */
1055
  private void writeDocumentToDB(String action, String user, String pub, 
1056
                                 String catalogid, int serverCode) 
1057
               throws SQLException, Exception {
1058
    String sysdate = dbAdapter.getDateTimeFunction();
1059

    
1060
    try {
1061
      PreparedStatement pstmt = null;
1062

    
1063
      if (action.equals("INSERT")) {
1064
        //AccessionNumber ac = new AccessionNumber();
1065
        //this.docid = ac.generate(docid, "INSERT");
1066
        pstmt = conn.prepareStatement(
1067
                "INSERT INTO xml_documents " +
1068
                "(docid, rootnodeid, docname, doctype, " + 
1069
                "user_owner, user_updated, date_created, date_updated, " + 
1070
                "public_access, catalog_id, server_location) " +
1071
                "VALUES (?, ?, ?, ?, ?, ?, " + sysdate + ", " + sysdate + 
1072
                ", ?, ?, ?)");
1073
        //note that the server_location is set to 1. 
1074
        //this means that "localhost" in the xml_replication table must
1075
        //always be the first entry!!!!!
1076
        
1077
        // Bind the values to the query
1078
        pstmt.setString(1, this.docid);
1079
        pstmt.setLong(2, rootnodeid);
1080
        pstmt.setString(3, docname);
1081
        pstmt.setString(4, doctype);
1082
        pstmt.setString(5, user);
1083
        pstmt.setString(6, user);
1084
        if ( pub == null ) {
1085
          pstmt.setString(7, null);
1086
        } else if ( pub.toUpperCase().equals("YES") || pub.equals("1") ) {
1087
          pstmt.setInt(7, 1);
1088
        } else if ( pub.toUpperCase().equals("NO") || pub.equals("0") ) {
1089
          pstmt.setInt(7, 0);
1090
        }
1091
        pstmt.setString(8, catalogid);
1092
        pstmt.setInt(9, serverCode);
1093
      } else if (action.equals("UPDATE")) {
1094

    
1095
        // Save the old document entry in a backup table
1096
        DocumentImpl.archiveDocRevision( conn, docid, user );
1097
        DocumentImpl thisdoc = new DocumentImpl(conn, docid);
1098
        int thisrev = thisdoc.getRev();
1099
        
1100
        //if the updated vesion is not greater than current one,
1101
        //throw it into a exception
1102
        if (Integer.parseInt(updatedVersion)<=thisrev)
1103
        {
1104
          throw new Exception("Next revision number couldn't be less"
1105
                               +" than or equal "+ thisrev);
1106
        }
1107
        else
1108
        {
1109
          //set the user specified revision 
1110
          thisrev=Integer.parseInt(updatedVersion);
1111
        }
1112
        
1113
        // Delete index for the old version of docid
1114
        // The new index is inserting on the next calls to DBSAXNode
1115
        pstmt = conn.prepareStatement(
1116
                "DELETE FROM xml_index WHERE docid='" + this.docid + "'");
1117
        pstmt.execute();
1118
        pstmt.close();
1119

    
1120
        // Update the new document to reflect the new node tree
1121
        pstmt = conn.prepareStatement(
1122
            "UPDATE xml_documents " +
1123
            "SET rootnodeid = ?, docname = ?, doctype = ?, " +
1124
            "user_updated = ?, date_updated = " + sysdate + ", " +
1125
            "server_location = ?, rev = ?, public_access = ?, catalog_id = ? " +
1126
            "WHERE docid = ?");
1127
        // Bind the values to the query
1128
        pstmt.setLong(1, rootnodeid);
1129
        pstmt.setString(2, docname);
1130
        pstmt.setString(3, doctype);
1131
        pstmt.setString(4, user);
1132
        pstmt.setInt(5, serverCode);
1133
        pstmt.setInt(6, thisrev);
1134
        if ( pub == null ) {
1135
          pstmt.setString(7, null);
1136
        } else if ( pub.toUpperCase().equals("YES") || pub.equals("1") ) {
1137
          pstmt .setInt(7, 1);
1138
        } else if ( pub.toUpperCase().equals("NO") || pub.equals("0") ) {
1139
          pstmt.setInt(7, 0);
1140
        }
1141
        pstmt.setString(8, catalogid);
1142
        pstmt.setString(9, this.docid);
1143

    
1144
      } else {
1145
        System.err.println("Action not supported: " + action);
1146
      }
1147

    
1148
      // Do the insertion
1149
      pstmt.execute();
1150
      pstmt.close();
1151

    
1152
    } catch (SQLException sqle) {
1153
      throw sqle;
1154
    } catch (Exception e) {
1155
      throw e;
1156
    }
1157
  }
1158

    
1159
  /**
1160
   * Write an XML file to the database, given a filename
1161
   *
1162
   * @param conn the JDBC connection to the database
1163
   * @param filename the filename to be loaded into the database
1164
   * @param pub flag for public "read" access on document
1165
   * @param dtdfilename the dtd to be uploaded on server's file system
1166
   * @param action the action to be performed (INSERT OR UPDATE)
1167
   * @param docid the docid to use for the INSERT OR UPDATE
1168
   * @param user the user that owns the document
1169
   * @param groups the groups to which user belongs
1170
   */
1171
  public static String write(Connection conn,String filename,
1172
                             String pub, String dtdfilename,
1173
                             String action, String docid, String user,
1174
                             String[] groups )
1175
                throws Exception {
1176
                  
1177
    Reader dtd = null;
1178
    if ( dtdfilename != null ) {
1179
      dtd = new FileReader(new File(dtdfilename).toString());
1180
    }
1181
    return write ( conn, new FileReader(new File(filename).toString()),
1182
                   pub, dtd, action, docid, user, groups, false);
1183
  }
1184

    
1185
  public static String write(Connection conn,Reader xml,String pub,Reader dtd,
1186
                             String action, String docid, String user,
1187
                             String[] groups, boolean validate)
1188
                throws Exception {
1189
    //this method will be called in handleUpdateOrInsert method 
1190
    //in MetacatServlet class
1191
    // get server location for this doc
1192
    int serverLocation=getServerLocationNumber(conn,docid);
1193
    //System.out.println("server location: "+serverLocation);
1194
    return write(conn,xml,pub,dtd,action,docid,user,groups,serverLocation,false,
1195
                 validate);
1196
  }
1197

    
1198
  public static String write(Connection conn, Reader xml, String pub,
1199
                             String action, String docid, String user,
1200
                             String[] groups )
1201
                throws Exception {
1202
    if(action.equals("UPDATE"))
1203
    {//if the document is being updated then use the servercode from the 
1204
     //originally inserted document.
1205
      DocumentImpl doc = new DocumentImpl(conn, docid);
1206
      int servercode = doc.getServerlocation();
1207
      return write(conn, xml, pub, action, docid, user, groups, servercode);
1208
    }
1209
    else
1210
    {//if the file is being inserted then the servercode is always 1
1211
      return write(conn, xml, pub, action, docid, user, groups, 1);
1212
    }
1213
  }
1214
  
1215
  public static String write( Connection conn, Reader xml,
1216
                              String action, String docid, String user,
1217
                              String[] groups, int serverCode )
1218
                throws Exception
1219
  {
1220
    return write(conn,xml,null,action,docid,user,groups,serverCode);
1221
  }
1222
  
1223
  public static String write( Connection conn, Reader xml, String pub,
1224
                              String action, String docid, String user,
1225
                              String[] groups, int serverCode) 
1226
                throws Exception
1227
  {
1228
    return write(conn,xml,pub,null,action,docid,user,groups,
1229
                 serverCode,false,false);
1230
  }
1231
  
1232
  public static String write( Connection conn, Reader xml, String pub,
1233
                              String action, String docid, String user,
1234
                              String[] groups, int serverCode, boolean override)
1235
                throws Exception
1236
  {
1237
    return write(conn,xml,pub,null,action,docid,user,groups,
1238
                 serverCode,override,false);
1239
  }
1240
  
1241
  /**
1242
   * Write an XML file to the database, given a Reader
1243
   *
1244
   * @param conn the JDBC connection to the database
1245
   * @param xml the xml stream to be loaded into the database
1246
   * @param pub flag for public "read" access on xml document
1247
   * @param dtd the dtd to be uploaded on server's file system
1248
   * @param action the action to be performed (INSERT or UPDATE)
1249
   * @param accnum the docid + rev# to use on INSERT or UPDATE
1250
   * @param user the user that owns the document
1251
   * @param groups the groups to which user belongs
1252
   * @param serverCode the serverid from xml_replication on which this document
1253
   *        resides.
1254
   * @param override flag to stop insert replication checking.
1255
   *        if override = true then a document not belonging to the local server
1256
   *        will not be checked upon update for a file lock.
1257
   *        if override = false then a document not from this server, upon 
1258
   *        update will be locked and version checked.
1259
   */
1260

    
1261
  public static String write( Connection conn,Reader xml,String pub,Reader dtd,
1262
                              String action, String accnum, String user,
1263
                              String[] groups, int serverCode, boolean override,
1264
                              boolean validate)
1265
                throws Exception
1266
  {
1267
    // NEW - WHEN CLIENT ALWAYS PROVIDE ACCESSION NUMBER INCLUDING REV IN IT
1268
    MetaCatUtil util = new MetaCatUtil();
1269
    AccessionNumber ac = new AccessionNumber(conn, accnum, action);
1270
    String docid = ac.getDocid();
1271
    String rev = ac.getRev();
1272
    MetaCatUtil.debugMessage("action: " + action + " servercode: " + 
1273
                             serverCode + " override: " + override);
1274
                        
1275
    if((serverCode != 1 && action.equals("UPDATE")) && !override)
1276
    { //if this document being written is not a resident of this server then
1277
      //we need to try to get a lock from it's resident server.  If the
1278
      //resident server will not give a lock then we send the user a message
1279
      //saying that he/she needs to download a new copy of the file and
1280
      //merge the differences manually.
1281
      int istreamInt; 
1282
      char istreamChar;
1283
      DocumentIdentifier id = new DocumentIdentifier(accnum);
1284
      String updaterev = id.getRev();
1285
      String server = MetacatReplication.getServer(serverCode);
1286
      MetacatReplication.replLog("attempting to lock " + accnum);
1287
      URL u = new URL("https://" + server + "?server="
1288
           +util.getLocalReplicationServerName()+"&action=getlock&updaterev=" 
1289
           +updaterev + "&docid=" + docid);
1290
      //System.out.println("sending message: " + u.toString());
1291
      String serverResStr = MetacatReplication.getURLContent(u);
1292
      String openingtag =serverResStr.substring(0, serverResStr.indexOf(">")+1);
1293
      if(openingtag.equals("<lockgranted>"))
1294
      {//the lock was granted go ahead with the insert
1295
        try 
1296
        {
1297
          //System.out.println("In lockgranted");
1298
          MetacatReplication.replLog("lock granted for " + accnum + " from " +
1299
                                      server);
1300
          XMLReader parser = initializeParser(conn, action, docid, updaterev,
1301
                                  validate, user, groups, pub, serverCode, dtd);
1302
          conn.setAutoCommit(false);
1303
          parser.parse(new InputSource(xml)); 
1304
          conn.commit();
1305
          conn.setAutoCommit(true);
1306
        } 
1307
        catch (Exception e) 
1308
        {
1309
          conn.rollback();
1310
          conn.setAutoCommit(true);
1311
          throw e;
1312
        }
1313
                
1314
        
1315
        
1316
        //If Metacat is super hub, tell all servers in its server list to get 
1317
        //the new document, ture mean it is xml document
1318
        if ((util.getOption("hub")).equals("super"))
1319
        {
1320
          ForceReplicationHandler frh = new ForceReplicationHandler(accnum, 
1321
                true, ReplicationHandler.buildServerList(conn));
1322
        }
1323
        else
1324
        {
1325
          //after inserting the document locally, tell the document's homeserver
1326
          //to come get a copy from here.
1327
         
1328
          ForceReplicationHandler frh = new ForceReplicationHandler(accnum, 
1329
                true, ReplicationHandler.getHomeServer(docid));
1330
        }
1331
        return (accnum);
1332
      }
1333

    
1334
      else if(openingtag.equals("<filelocked>"))
1335
      {//the file is currently locked by another user
1336
       //notify our user to wait a few minutes, check out a new copy and try
1337
       //again.
1338
        //System.out.println("file locked");
1339
        MetacatReplication.replLog("lock denied for " + accnum + " on " +
1340
                                   server + " reason: file already locked");
1341
        throw new Exception("The file specified is already locked by another " +
1342
                            "user.  Please wait 30 seconds, checkout the " +
1343
                            "newer document, merge your changes and try " +
1344
                            "again.");
1345
      }
1346
      else if(openingtag.equals("<outdatedfile>"))
1347
      {//our file is outdated.  notify our user to check out a new copy of the
1348
       //file and merge his version with the new version.
1349
        //System.out.println("outdated file");
1350
        MetacatReplication.replLog("lock denied for " + accnum + " on " +
1351
                                    server + " reason: local file outdated");
1352
        throw new Exception("The file you are trying to update is an outdated" +
1353
                            " version.  Please checkout the newest document, " +
1354
                            "merge your changes and try again.");
1355
      }
1356
    }
1357
    
1358
    if ( action.equals("UPDATE") ) {
1359
      // check for 'write' permission for 'user' to update this document
1360

    
1361
      if ( !hasPermission(conn, user, groups, docid) ) {
1362
        throw new Exception("User " + user + 
1363
              " does not have permission to update XML Document #" + accnum);
1364
      }          
1365
    }
1366

    
1367
    try 
1368
    { 
1369
      
1370
      XMLReader parser = initializeParser(conn, action, docid, rev, validate,
1371
                                          user, groups, pub, serverCode, dtd);
1372
      conn.setAutoCommit(false);
1373
      parser.parse(new InputSource(xml));
1374
      conn.commit();
1375
      conn.setAutoCommit(true);
1376
    } 
1377
    catch (Exception e) 
1378
    {
1379
      conn.rollback();
1380
      conn.setAutoCommit(true);
1381
      throw e;
1382
    }
1383
    
1384
    //force replicate out the new document to each server in our server list.
1385
    if(serverCode == 1)
1386
    { 
1387
      
1388
      //start the thread to replicate this new document out to the other servers
1389
      //true mean it is xml document
1390
      ForceReplicationHandler frh = new ForceReplicationHandler
1391
                (accnum, action, true,ReplicationHandler.buildServerList(conn));
1392
      
1393
    }
1394
      
1395
    return(accnum);
1396
  }
1397

    
1398
  /**
1399
   * Delete an XML file from the database (actually, just make it a revision
1400
   * in the xml_revisions table)
1401
   *
1402
   * @param docid the ID of the document to be deleted from the database
1403
   */
1404
  public static void delete( Connection conn, String accnum,
1405
                                 String user, String[] groups )
1406
                throws Exception 
1407
  {
1408
    // OLD
1409
    //DocumentIdentifier id = new DocumentIdentifier(accnum);
1410
    //String docid = id.getIdentifier();
1411
    //String rev = id.getRev();
1412
    
1413
    // OLD
1414
    // Determine if the docid,rev are OK for DELETE
1415
    //AccessionNumber ac = new AccessionNumber(conn);
1416
    //docid = ac.generate(docid, rev, "DELETE");
1417

    
1418
    // NEW - WHEN CLIENT ALWAYS PROVIDE ACCESSION NUMBER INCLUDING REV IN IT
1419
    AccessionNumber ac = new AccessionNumber(conn, accnum, "DELETE");
1420
    String docid = ac.getDocid();
1421
    String rev = ac.getRev();
1422
    
1423

    
1424
    // check for 'write' permission for 'user' to delete this document
1425
    if ( !hasPermission(conn, user, groups, docid) ) {
1426
      throw new Exception("User " + user + 
1427
              " does not have permission to delete XML Document #" + accnum);
1428
    }
1429

    
1430
    conn.setAutoCommit(false);
1431
    // Copy the record to the xml_revisions table
1432
    DocumentImpl.archiveDocRevision( conn, docid, user );
1433

    
1434
    // Now delete it from the xml_documents table
1435
    
1436
    Statement stmt = conn.createStatement();
1437
    stmt.execute("DELETE FROM xml_index WHERE docid = '" + docid + "'");
1438
    //stmt.execute("DELETE FROM xml_access WHERE docid = '" + docid + "'");
1439
    stmt.execute("DELETE FROM xml_access WHERE accessfileid = '" + docid + "'");
1440
    stmt.execute("DELETE FROM xml_relation WHERE docid = '" + docid + "'");
1441
    stmt.execute("DELETE FROM xml_documents WHERE docid = '" + docid + "'");
1442
    stmt.close();
1443
    conn.commit();
1444
    conn.setAutoCommit(true);
1445
    //IF this is a package document:
1446
    //delete all of the relations that this document created.
1447
    //if the deleted document is a package document its relations should 
1448
    //no longer be active if it has been deleted from the system.
1449
    
1450
  }
1451

    
1452
  /** 
1453
    * Check for "WRITE" permission on @docid for @user and/or @groups 
1454
    * from DB connection 
1455
    */
1456
  private static boolean hasPermission ( Connection conn, String user,
1457
                                  String[] groups, String docid ) 
1458
                  throws SQLException, Exception
1459
  {
1460
    // Check for WRITE permission on @docid for @user and/or @groups
1461
    AccessControlList aclobj = new AccessControlList(conn);
1462
    return aclobj.hasPermission("WRITE", user, groups, docid);
1463
  }
1464

    
1465
  /** 
1466
    * Check for "READ" permission base on docid, user and group
1467
    *@docid, the document
1468
    *@user, user name
1469
    *@group, user's group
1470
    * 
1471
    */
1472
  public boolean hasReadPermission ( Connection conn, String user,
1473
                                  String[] groups, String docId ) 
1474
                  throws SQLException, Exception
1475
  {
1476
    // Check for READ permission on @docid for @user and/or @groups
1477
    AccessControlList aclObj = new AccessControlList(conn);
1478
    return aclObj.hasPermission("READ", user, groups, docId);
1479
  }  
1480

    
1481
  /**
1482
   * Set up the parser handlers for writing the document to the database
1483
   */
1484
  private static XMLReader initializeParser(Connection conn, String action,
1485
                               String docid, String rev, boolean validate, 
1486
                                   String user, String[] groups, String pub, 
1487
                                   int serverCode, Reader dtd) 
1488
                           throws Exception 
1489
  {
1490
    XMLReader parser = null;
1491
    //
1492
    // Set up the SAX document handlers for parsing
1493
    //
1494
    try {
1495
      //create a DBSAXHandler object which has the revision specification
1496
      ContentHandler chandler = new DBSAXHandler(conn, action, docid, rev,
1497
                                                 user, groups, pub, serverCode);
1498
      EntityResolver eresolver= new DBEntityResolver(conn,
1499
                                                 (DBSAXHandler)chandler, dtd);
1500
      DTDHandler dtdhandler   = new DBDTDHandler(conn);
1501

    
1502
      // Get an instance of the parser
1503
      String parserName = MetaCatUtil.getOption("saxparser");
1504
      parser = XMLReaderFactory.createXMLReader(parserName);
1505

    
1506
      // Turn on validation
1507
      parser.setFeature("http://xml.org/sax/features/validation", validate);
1508
      // Turn off Including all external parameter entities
1509
      // (the external DTD subset also)
1510
      // Doesn't work well, probably the feature name is not correct
1511
      // parser.setFeature(
1512
      //  "http://xml.org/sax/features/external-parameter-entities", false);
1513
      
1514
      // Set Handlers in the parser
1515
      parser.setProperty("http://xml.org/sax/properties/declaration-handler",
1516
                         chandler);
1517
      parser.setProperty("http://xml.org/sax/properties/lexical-handler",
1518
                         chandler);
1519
      parser.setContentHandler((ContentHandler)chandler);
1520
      parser.setEntityResolver((EntityResolver)eresolver);
1521
      parser.setDTDHandler((DTDHandler)dtdhandler);
1522
      parser.setErrorHandler((ErrorHandler)chandler);
1523

    
1524
    } catch (Exception e) {
1525
      throw e;
1526
    }
1527

    
1528
    return parser;
1529
  }
1530

    
1531
  /** Save a document entry in the xml_revisions table */
1532
  private static void archiveDocRevision(Connection conn, String docid,
1533
                                         String user) 
1534
                                         throws SQLException {
1535
    String sysdate = dbAdapter.getDateTimeFunction();
1536
    // create a record in xml_revisions table 
1537
    // for that document as selected from xml_documents
1538
    PreparedStatement pstmt = conn.prepareStatement(
1539
      "INSERT INTO xml_revisions " +
1540
        "(docid, rootnodeid, docname, doctype, " +
1541
        "user_owner, user_updated, date_created, date_updated, " +
1542
        "server_location, rev, public_access, catalog_id) " +
1543
      "SELECT ?, rootnodeid, docname, doctype, " + 
1544
        "user_owner, ?, " + sysdate + ", " + sysdate + ", "+
1545
        "server_location, rev, public_access, catalog_id " +
1546
      "FROM xml_documents " +
1547
      "WHERE docid = ?");
1548
    // Bind the values to the query and execute it
1549
    pstmt.setString(1, docid);
1550
    pstmt.setString(2, user);
1551
    pstmt.setString(3, docid);
1552
    pstmt.execute();
1553
    pstmt.close();
1554

    
1555
  }
1556
  
1557
  /**
1558
    * delete a entry in xml_table for given docid
1559
    * @param docId, the id of the document need to be delete
1560
    */
1561
  private static void deleteXMLDocuments(Connection conn, String docId) 
1562
                                         throws SQLException 
1563
  {
1564
    //delete a record 
1565
    PreparedStatement pStmt = 
1566
             conn.prepareStatement("DELETE FROM xml_documents WHERE docid = '" 
1567
                                              + docId + "'");
1568
    pStmt.execute();
1569
    pStmt.close();
1570

    
1571
  }//deleteXMLDocuments
1572
  
1573
  /**
1574
    * Get last revision number from database for a docid
1575
    * If couldn't find an entry,  -1 will return
1576
    * The return value is integer because we want compare it to there new one
1577
    * @param docid <sitecode>.<uniqueid> part of Accession Number
1578
    */
1579
  private static int getLatestRevisionNumber(Connection conn, String docId)
1580
                                      throws SQLException
1581
  {
1582
    int rev = 1;
1583
    PreparedStatement pStmt;
1584
     
1585
    pStmt = conn.prepareStatement
1586
              ("SELECT rev FROM xml_documents WHERE docid='" + docId + "'");
1587
    pStmt.execute();
1588

    
1589
    ResultSet rs = pStmt.getResultSet();
1590
    boolean hasRow = rs.next();
1591
    if (hasRow)
1592
    {
1593
      rev = rs.getInt(1);
1594
      pStmt.close();
1595
    }
1596
    else
1597
    {
1598
      rev=-1;
1599
      pStmt.close();
1600
    }
1601
      
1602
    return rev;
1603
  }//getLatestRevisionNumber
1604
  
1605
  /**
1606
   * Get server location form database for a accNum
1607
   * 
1608
   * @param accum <sitecode>.<uniqueid>.<rev>
1609
   */
1610
  private static int getServerLocationNumber(Connection conn, String accNum)
1611
                                            throws SQLException
1612
  {
1613
    //get rid of revNum part
1614
    String docId=MetaCatUtil.getDocIdFromString(accNum);
1615
    PreparedStatement pStmt;
1616
    int serverLocation;
1617
     
1618
    pStmt = conn.prepareStatement
1619
      ("SELECT server_location FROM xml_documents WHERE docid='" + docId + "'");
1620
    pStmt.execute();
1621

    
1622
    ResultSet rs = pStmt.getResultSet();
1623
    boolean hasRow = rs.next();
1624
    //if there is entry in xml_documents, get the serverlocation
1625
    if (hasRow)
1626
    {
1627
      serverLocation = rs.getInt(1);
1628
      pStmt.close();
1629
    }
1630
    else
1631
    {
1632
      // if htere is no entry in xml_documents, we consider it is new document
1633
      // the server location is local host and value is 1
1634
      serverLocation=1;
1635
      pStmt.close();
1636
    }
1637
      
1638
    return serverLocation;
1639
  }
1640
  /**
1641
   * the main routine used to test the DBWriter utility.
1642
   * <p>
1643
   * Usage: java DocumentImpl <-f filename -a action -d docid>
1644
   *
1645
   * @param filename the filename to be loaded into the database
1646
   * @param action the action to perform (READ, INSERT, UPDATE, DELETE)
1647
   * @param docid the id of the document to process
1648
   */
1649
  static public void main(String[] args) {
1650
     
1651
    try {
1652
      String filename    = null;
1653
      String dtdfilename = null;
1654
      String action      = null;
1655
      String docid       = null;
1656
      boolean showRuntime = false;
1657
      boolean useOldReadAlgorithm = false;
1658

    
1659
      // Parse the command line arguments
1660
      for ( int i=0 ; i < args.length; ++i ) {
1661
        if ( args[i].equals( "-f" ) ) {
1662
          filename =  args[++i];
1663
        } else if ( args[i].equals( "-r" ) ) {
1664
          dtdfilename =  args[++i];
1665
        } else if ( args[i].equals( "-a" ) ) {
1666
          action =  args[++i];
1667
        } else if ( args[i].equals( "-d" ) ) {
1668
          docid =  args[++i];
1669
        } else if ( args[i].equals( "-t" ) ) {
1670
          showRuntime = true;
1671
        } else if ( args[i].equals( "-old" ) ) {
1672
          useOldReadAlgorithm = true;
1673
        } else {
1674
          System.err.println
1675
            ( "   args[" +i+ "] '" +args[i]+ "' ignored." );
1676
        }
1677
      }
1678
      
1679
      // Check if the required arguments are provided
1680
      boolean argsAreValid = false;
1681
      if (action != null) {
1682
        if (action.equals("INSERT")) {
1683
          if (filename != null) {
1684
            argsAreValid = true;
1685
          } 
1686
        } else if (action.equals("UPDATE")) {
1687
          if ((filename != null) && (docid != null)) {
1688
            argsAreValid = true;
1689
          } 
1690
        } else if (action.equals("DELETE")) {
1691
          if (docid != null) {
1692
            argsAreValid = true;
1693
          } 
1694
        } else if (action.equals("READ")) {
1695
          if (docid != null) {
1696
            argsAreValid = true;
1697
          } 
1698
        } 
1699
      } 
1700

    
1701
      // Print usage message if the arguments are not valid
1702
      if (!argsAreValid) {
1703
        System.err.println("Wrong number of arguments!!!");
1704
        System.err.println(
1705
          "USAGE: java DocumentImpl [-t] <-a INSERT> [-d docid] <-f filename> "+
1706
          "[-r dtdfilename]");
1707
        System.err.println(
1708
          "   OR: java DocumentImpl [-t] <-a UPDATE -d docid -f filename> " +
1709
          "[-r dtdfilename]");
1710
        System.err.println(
1711
          "   OR: java DocumentImpl [-t] <-a DELETE -d docid>");
1712
        System.err.println(
1713
          "   OR: java DocumentImpl [-t] [-old] <-a READ -d docid>");
1714
        return;
1715
      }
1716
      
1717
      // Time the request if asked for
1718
      double startTime = System.currentTimeMillis();
1719
      
1720
      // Open a connection to the database
1721
      MetaCatUtil util = new MetaCatUtil();
1722
      Connection dbconn = util.openDBConnection();
1723

    
1724
      double connTime = System.currentTimeMillis();
1725
      // Execute the action requested (READ, INSERT, UPDATE, DELETE)
1726
      if (action.equals("READ")) {
1727
          DocumentImpl xmldoc = new DocumentImpl( dbconn, docid );
1728
          if (useOldReadAlgorithm) {
1729
            System.out.println(xmldoc.readUsingSlowAlgorithm());
1730
          } else {
1731
            xmldoc.toXml(new PrintWriter(System.out));
1732
          }
1733
      } else if (action.equals("DELETE")) {
1734
        DocumentImpl.delete(dbconn, docid, null, null);
1735
        System.out.println("Document deleted: " + docid);
1736
      } else {
1737
        String newdocid = DocumentImpl.write(dbconn, filename, null,
1738
                                             dtdfilename, action, docid,
1739
                                             null, null);
1740
        if ((docid != null) && (!docid.equals(newdocid))) {
1741
          if (action.equals("INSERT")) {
1742
            System.out.println("New document ID generated!!! ");
1743
          } else if (action.equals("UPDATE")) {
1744
            System.out.println("ERROR: Couldn't update document!!! ");
1745
          }
1746
        } else if ((docid == null) && (action.equals("UPDATE"))) {
1747
          System.out.println("ERROR: Couldn't update document!!! ");
1748
        }
1749
        System.out.println("Document processing finished for: " + filename
1750
              + " (" + newdocid + ")");
1751
      }
1752

    
1753
      double stopTime = System.currentTimeMillis();
1754
      double dbOpenTime = (connTime - startTime)/1000;
1755
      double insertTime = (stopTime - connTime)/1000;
1756
      double executionTime = (stopTime - startTime)/1000;
1757
      if (showRuntime) {
1758
        System.out.println("\n\nTotal Execution time was: " + 
1759
                           executionTime + " seconds.");
1760
        System.out.println("Time to open DB connection was: " + dbOpenTime + 
1761
                           " seconds.");
1762
        System.out.println("Time to insert document was: " + insertTime +
1763
                           " seconds.");
1764
      }
1765
      dbconn.close();
1766
    } catch (McdbException me) {
1767
      me.toXml(new PrintWriter(System.err));
1768
    } catch (AccessionNumberException ane) {
1769
      System.out.println(ane.getMessage());
1770
    } catch (Exception e) {
1771
      System.err.println("EXCEPTION HANDLING REQUIRED");
1772
      System.err.println(e.getMessage());
1773
      e.printStackTrace(System.err);
1774
    }
1775
  }
1776
}
(24-24/41)