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: jones $'
10
 *     '$Date: 2000-08-24 16:47:35 -0700 (Thu, 24 Aug 2000) $'
11
 * '$Revision: 407 $'
12
 */
13

    
14
package edu.ucsb.nceas.metacat;
15

    
16
import java.sql.*;
17
import java.io.PrintWriter;
18
import java.util.TreeSet;
19

    
20
import java.io.*;
21
import java.util.Stack;
22

    
23
import org.xml.sax.AttributeList;
24
import org.xml.sax.ContentHandler;
25
import org.xml.sax.DTDHandler;
26
import org.xml.sax.EntityResolver;
27
import org.xml.sax.ErrorHandler;
28
import org.xml.sax.InputSource;
29
import org.xml.sax.XMLReader;
30
import org.xml.sax.SAXException;
31
import org.xml.sax.SAXParseException;
32
import org.xml.sax.helpers.XMLReaderFactory;
33

    
34
/**
35
 * A class that represents an XML document. It can be created with a simple
36
 * document identifier from a database connection.  It also will write an
37
 * XML text document to a database connection using SAX.
38
 */
39
public class DocumentImpl {
40

    
41
  private Connection conn = null;
42
  private MetaCatUtil util = null;
43
  private String docid = null;
44
  private String docname = null;
45
  private String doctype = null;
46
  private String system_id = null;
47
  private long rootnodeid;
48
  private ElementNode rootNode = null;
49
  private String doctitle = null;
50
  private String parserName = null;
51

    
52
  /**
53
   * Constructor, creates document from database connection, used 
54
   * for reading the document
55
   *
56
   * @param conn the database connection from which to read the document
57
   * @param docid the identifier of the document to be created
58
   */
59
  public DocumentImpl(Connection conn, String docid) throws McdbException 
60
  {
61
    try {
62
      this.conn = conn;
63
      this.docid = docid;
64
  
65
      // Look up the document information
66
      getDocumentInfo(docid);
67
      
68
      // Download all of the document nodes using a single SQL query
69
      TreeSet nodeRecordList = getNodeRecordList(rootnodeid);
70
  
71
      // Create the elements from the downloaded data in the TreeSet
72
      rootNode = new ElementNode(nodeRecordList, rootnodeid);
73

    
74
    } catch (McdbException ex) {
75
      throw ex;
76
    } catch (Throwable t) {
77
      throw new McdbException("Error reading document " + docid + ".");
78
    }
79
  }
80

    
81
  /** 
82
   * Construct a new document instance, writing the contents to the database
83
   *
84
   * @param conn the JDBC Connection to which all information is written
85
   * @param rootnodeid - sequence id of the root node in the document
86
   * @param docname - the name of DTD, i.e. the name immediately following 
87
   *        the DOCTYPE keyword ( should be the root element name ) or
88
   *        the root element name if no DOCTYPE declaration provided
89
   *        (Oracle's and IBM parsers are not aware if it is not the 
90
   *        root element name)
91
   * @param doctype - Public ID of the DTD, i.e. the name immediately 
92
   *                  following the PUBLIC keyword in DOCTYPE declaration or
93
   *                  the docname if no Public ID provided or
94
   *                  null if no DOCTYPE declaration provided
95
   *
96
   */
97
  public DocumentImpl(Connection conn, long rootnodeid, String docname, 
98
                      String doctype, String docid, String action)
99
                      throws AccessionNumberException 
100
  {
101
    this.conn = conn;
102
    this.rootnodeid = rootnodeid;
103
    this.docname = docname;
104
    this.doctype = doctype;
105
    this.docid = docid;
106
    writeDocumentToDB(action);
107
  }
108

    
109
  /** 
110
   * Construct a new document instance, used for deleting documents
111
   *
112
   * @param conn the JDBC Connection to which all information is written
113
   */
114
  public DocumentImpl (Connection conn) 
115
  {
116
    this.conn = conn;
117
    this.util = new MetaCatUtil();
118
    this.parserName = util.getOption("saxparser");
119
  }
120

    
121
  /**
122
   * get the document name
123
   */
124
  public String getDocname() {
125
    return docname;
126
  }
127

    
128
  /**
129
   * get the document type (which is the PublicID)
130
   */
131
  public String getDoctype() {
132
    return doctype;
133
  }
134

    
135
  /**
136
   * get the system identifier
137
   */
138
  public String getSystemID() {
139
    return system_id;
140
  }
141

    
142
  /**
143
   * get the root node identifier
144
   */
145
  public long getRootNodeID() {
146
    return rootnodeid;
147
  }
148

    
149
  /** 
150
   * Get the document identifier (docid)
151
   */
152
  public String getDocID() {
153
    return docid;
154
  }
155

    
156
  /**
157
   * Create an XML document from the database for the document with ID docid
158
   */
159
  public String toString()
160
  {
161
    StringBuffer doc = new StringBuffer();
162

    
163
    // Append the resulting document to the StringBuffer and return it
164
    doc.append("<?xml version=\"1.0\"?>\n");
165
      
166
    if (docname != null) {
167
      if ((doctype != null) && (system_id != null)) {
168
        doc.append("<!DOCTYPE " + docname + " PUBLIC \"" + doctype + 
169
                   "\" \"" + system_id + "\">\n");
170
      } else {
171
        doc.append("<!DOCTYPE " + docname + ">\n");
172
      }
173
    }
174
    doc.append(rootNode.toString());
175
  
176
    return (doc.toString());
177
  }
178

    
179
  /**
180
   * Look up the document type information from the database
181
   *
182
   * @param docid the id of the document to look up
183
   */
184
  private void getDocumentInfo(String docid) throws McdbException 
185
  {
186
    PreparedStatement pstmt;
187

    
188
    try {
189
      pstmt =
190
        conn.prepareStatement("SELECT docname,doctype,rootnodeid " +
191
                                "FROM xml_documents " +
192
                               "WHERE docid = ?");
193
      // Bind the values to the query
194
      pstmt.setString(1, docid);
195

    
196
      pstmt.execute();
197
      ResultSet rs = pstmt.getResultSet();
198
      boolean tableHasRows = rs.next();
199
      if (tableHasRows) {
200
        this.docname    = rs.getString(1);
201
        this.doctype    = rs.getString(2);
202
        this.rootnodeid = rs.getLong(3);
203
      } 
204
      pstmt.close();
205

    
206
      if (this.doctype != null) {
207
        pstmt =
208
          conn.prepareStatement("SELECT system_id " +
209
                                  "FROM xml_catalog " +
210
                                 "WHERE public_id = ?");
211
        // Bind the values to the query
212
        pstmt.setString(1, doctype);
213
  
214
        pstmt.execute();
215
        rs = pstmt.getResultSet();
216
        tableHasRows = rs.next();
217
        if (tableHasRows) {
218
          this.system_id  = rs.getString(1);
219
        } 
220
        pstmt.close();
221
      }
222
    } catch (SQLException e) {
223
      throw new McdbException("Error accessing database connection.", e);
224
    }
225

    
226
    if (this.docname == null) {
227
      throw new McdbDocNotFoundException("Document not found: " + docid);
228
    }
229
  }
230

    
231
  /**
232
   * Look up the node data from the database
233
   *
234
   * @param rootnodeid the id of the root node of the node tree to look up
235
   */
236
  private TreeSet getNodeRecordList(long rootnodeid) throws McdbException 
237
  {
238
    PreparedStatement pstmt;
239
    TreeSet nodeRecordList = new TreeSet(new NodeComparator());
240
    long nodeid = 0;
241
    long parentnodeid = 0;
242
    long nodeindex = 0;
243
    String nodetype = null;
244
    String nodename = null;
245
    String nodedata = null;
246

    
247
    try {
248
      pstmt =
249
      conn.prepareStatement("SELECT nodeid,parentnodeid,nodeindex, " +
250
           "nodetype,nodename,"+               
251
           "replace(" +
252
           "replace(" +
253
           "replace(nodedata,'&','&amp;') " +
254
           ",'<','&lt;') " +
255
           ",'>','&gt;') " +
256
           "FROM xml_nodes WHERE rootnodeid = ?");
257

    
258
      // Bind the values to the query
259
      pstmt.setLong(1, rootnodeid);
260

    
261
      pstmt.execute();
262
      ResultSet rs = pstmt.getResultSet();
263
      boolean tableHasRows = rs.next();
264
      while (tableHasRows) {
265
        nodeid = rs.getLong(1);
266
        parentnodeid = rs.getLong(2);
267
        nodeindex = rs.getLong(3);
268
        nodetype = rs.getString(4);
269
        nodename = rs.getString(5);
270
        nodedata = rs.getString(6);
271

    
272
        // add the data to the node record list hashtable
273
        NodeRecord currentRecord = new NodeRecord(nodeid, parentnodeid, 
274
                                   nodeindex, nodetype, nodename, nodedata);
275
        nodeRecordList.add(currentRecord);
276

    
277
        // Advance to the next node
278
        tableHasRows = rs.next();
279
      } 
280
      pstmt.close();
281

    
282
    } catch (SQLException e) {
283
      throw new McdbException("Error accessing database connection.", e);
284
    }
285

    
286
    if (nodeRecordList != null) {
287
      return nodeRecordList;
288
    } else {
289
      throw new McdbException("Error getting node data: " + docid);
290
    }
291
  }
292

    
293
  /**
294
   * Write an XML file to the database, given a filename
295
   *
296
   * @param filename the filename to be loaded into the database
297
   * @param action the action to be performed (INSERT OR UPDATE)
298
   * @param docid the docid to use for the INSERT OR UPDATE
299
   */
300
  public String write( String filename, String action, String docid )
301
                throws Exception, IOException, SQLException, 
302
                       ClassNotFoundException, SAXException, SAXParseException {
303
     return write(new FileReader(new File(filename).toString()), action, docid);
304
  }
305
  
306
  /**
307
   * Write an XML file to the database, given a Reader
308
   *
309
   * @param xml the xml stream to be loaded into the database
310
   * @param action the action to be performed (INSERT OR UPDATE)
311
   * @param docid the docid to use for the INSERT OR UPDATE
312
   */
313
  public String write( Reader xml, String action, String docid )
314
                throws Exception, IOException, SQLException, 
315
                       ClassNotFoundException, SAXException, SAXParseException {
316
    try {
317
        XMLReader parser = initializeParser(action, docid);
318
        conn.setAutoCommit(false);
319
        parser.parse(new InputSource(xml));
320
        conn.commit();
321
        conn.setAutoCommit(true);
322
        return docid;
323
      } catch (SAXParseException e) {
324
        conn.rollback();
325
        throw e;
326
      } catch (SAXException e) {
327

    
328
        // If its a problem with the accession number its ok, just the 
329
        // accession number was regenerated
330
        AccessionNumberGeneratedException ang = null;
331
        try {
332
          Exception embedded = e.getException();
333
          if ((embedded != null) && 
334
              (embedded instanceof AccessionNumberGeneratedException)) {
335
            ang = (AccessionNumberGeneratedException)e.getException();
336
          }
337
        } catch (ClassCastException cce) {
338
          // Do nothing and just fall through to the ang != null test
339
        }
340
        if (ang != null) {
341
          conn.commit();
342
          conn.setAutoCommit(true);
343
          return (ang.getMessage());
344
        } else {
345
          conn.rollback();
346
          throw e;
347
        }
348
      } catch (Exception e) {
349
        conn.rollback();
350
        throw e;
351
      }
352
  }
353

    
354
  /**
355
   * Delete an XML file from the database (actually, just make it a revision
356
   * in the xml_revisions table)
357
   *
358
   * @param docid the ID of the document to be deleted from the database
359
   */
360
  public void delete( String docid )
361
                  throws IOException, SQLException, 
362
                         ClassNotFoundException, AccessionNumberException {
363

    
364
    String newdocid = AccessionNumber.generate(docid, "DELETE");
365

    
366
    conn.setAutoCommit(false);
367
    // Copy the record to the xml_revisions table
368
    DocumentImpl document = new DocumentImpl(conn);
369
    document.archiveDocRevision( docid );
370

    
371
    // Now delete it from the xml_documents table
372
    Statement stmt = conn.createStatement();
373
    stmt.execute("DELETE FROM xml_index WHERE docid LIKE '" + docid + "'");
374
    stmt.execute("DELETE FROM xml_documents WHERE docid LIKE '" + docid + "'");
375
    stmt.close();
376
    conn.commit();
377
  }
378

    
379
  /** creates SQL code and inserts new document into DB connection */
380
  private void writeDocumentToDB(String action) 
381
               throws AccessionNumberException {
382
    try {
383
      PreparedStatement pstmt = null;
384

    
385
      if (action.equals("INSERT")) {
386
        this.docid = AccessionNumber.generate(docid, "INSERT");
387
        pstmt = conn.prepareStatement(
388
            "INSERT INTO xml_documents " +
389
            "(docid, rootnodeid, docname, doctype, " +
390
            "date_created, date_updated) " +
391
            "VALUES (?, ?, ?, ?, sysdate, sysdate)");
392
        // Bind the values to the query
393
        pstmt.setString(1, this.docid);
394
        pstmt.setLong(2, rootnodeid);
395
        pstmt.setString(3, docname);
396
        pstmt.setString(4, doctype);
397
      } else if (action.equals("UPDATE")) {
398

    
399
        // Determine if the docid is OK for an UPDATE
400
        this.docid = AccessionNumber.generate(docid, "UPDATE");
401

    
402
        // Save the old document entry in a backup table
403
        archiveDocRevision(docid);
404

    
405
        // Update the new document to reflect the new node tree
406
        pstmt = conn.prepareStatement(
407
            "UPDATE xml_documents " +
408
            "SET rootnodeid = ?, docname = ?, doctype = ?, " +
409
            "date_updated = sysdate WHERE docid LIKE ?");
410
        // Bind the values to the query
411
        pstmt.setLong(1, rootnodeid);
412
        pstmt.setString(2, docname);
413
        pstmt.setString(3, doctype);
414
        pstmt.setString(4, this.docid);
415
      } else {
416
        System.err.println("Action not supported: " + action);
417
      }
418

    
419
      // Do the insertion
420
      pstmt.execute();
421
      pstmt.close();
422

    
423
    } catch (SQLException e) {
424
      System.out.println(e.getMessage());
425
    } catch (AccessionNumberException ane) {
426
      MetaCatUtil.debugMessage("Invalid accession number.");
427
      MetaCatUtil.debugMessage(ane.getMessage());
428
      throw ane;
429
    } catch (Exception e) {
430
      System.out.println(e.getMessage());
431
    }
432
  }
433

    
434
  /**
435
   * Get the document title
436
   */
437
  public String getTitle() {
438
    return doctitle;
439
  }
440

    
441
  /**
442
   * Set the document title
443
   *
444
   * @param title the new title for the document
445
   */
446
  public void setTitle( String title ) {
447
    this.doctitle = title;
448
    try {
449
      PreparedStatement pstmt;
450
      pstmt = conn.prepareStatement(
451
            "UPDATE xml_documents " +
452
            " SET doctitle = ? " +
453
            "WHERE docid = ?");
454

    
455
      // Bind the values to the query
456
      pstmt.setString(1, doctitle);
457
      pstmt.setString(2, docid);
458

    
459
      // Do the insertion
460
      pstmt.execute();
461
      pstmt.close();
462
    } catch (SQLException e) {
463
      System.out.println(e.getMessage());
464
    }
465
  }
466

    
467
  /**
468
   * Look up the title of the first child element named "title"
469
   * and record it as the document title
470
   */
471
  public void setTitleFromChildElement() {
472
    String title = null;
473
    long assigned_id=0;
474
    PreparedStatement pstmt;
475
    try {
476
      pstmt = conn.prepareStatement(
477
              "SELECT nodedata FROM xml_nodes " +
478
              "WHERE nodetype = 'TEXT' " +
479
              "AND rootnodeid = ? " +
480
              "AND parentnodeid IN " +
481
              "  (SELECT nodeid FROM xml_nodes " +
482
              "  WHERE nodename = 'title' " +
483
              "  AND nodetype =  'ELEMENT' " +
484
              "  AND rootnodeid = ? ) " +
485
              "ORDER BY nodeid");
486

    
487
      // The above query might be slow, and probably will be because
488
      // it gets ALL of the title elements while searching for one
489
      // title in a small subtree but it avoids the problem of using
490
      // Oracle's Hierarchical Query syntax which is not portable --
491
      // the commented out SQL that follows shows an equivalent query
492
      // using Oracle-specific hierarchical query
493
      /*
494
      pstmt = conn.prepareStatement(
495
              "SELECT nodedata FROM xml_nodes " +
496
              "WHERE nodetype = 'TEXT' " +
497
              "AND parentnodeid IN " +
498
              "(SELECT nodeid FROM xml_nodes " +
499
              "WHERE nodename = 'title' " +
500
              "START WITH nodeid = ? " +
501
              "CONNECT BY PRIOR nodeid = parentnodeid)");
502
      */
503

    
504
      // Bind the values to the query
505
      pstmt.setLong(1, rootnodeid);
506
      pstmt.setLong(2, rootnodeid);
507

    
508
      pstmt.execute();
509
      ResultSet rs = pstmt.getResultSet();
510
      boolean tableHasRows = rs.next();
511
      if (tableHasRows) {
512
        title = rs.getString(1);
513
      }
514
      pstmt.close();
515
    } catch (SQLException e) {
516
      System.out.println("Error getting title: " + e.getMessage());
517
    }
518

    
519
    // assign the new title
520
    this.setTitle(title);
521
  }
522

    
523
  /** Save a document entry in the xml_revisions table */
524
  private void archiveDocRevision(String docid) throws SQLException {
525
    // First get all of the values we need
526
    long rnodeid = -1;
527
    String docname = null;
528
    String doctype = null;
529
    String doctitle = null;
530
    Date date_created = null;
531
    PreparedStatement pstmt = conn.prepareStatement(
532
       "SELECT rootnodeid, docname, doctype, doctitle, date_created " +
533
       "FROM xml_documents " +
534
       "WHERE docid = ?");
535
    // Bind the values to the query and execute it
536
    pstmt.setString(1, docid);
537
    pstmt.execute();
538

    
539
    ResultSet rs = pstmt.getResultSet();
540
    boolean tableHasRows = rs.next();
541
    if (tableHasRows) {
542
      rnodeid      = rs.getLong(1);
543
      docname      = rs.getString(2);
544
      doctype      = rs.getString(3);
545
      doctitle     = rs.getString(4);
546
      date_created = rs.getDate(5);
547
    }
548
    pstmt.close();
549

    
550
    MetaCatUtil.debugMessage(new Long(rnodeid).toString());
551
    MetaCatUtil.debugMessage(docname);
552
    MetaCatUtil.debugMessage(doctitle);
553
    //MetaCatUtil.debugMessage(date_created.toString());
554

    
555
    // Next create the new record in the other table using the values selected
556
    pstmt = conn.prepareStatement(
557
       "INSERT INTO xml_revisions " +
558
       "(revisionid, docid, rootnodeid, docname, doctype, doctitle, " +
559
       "date_created, date_updated) " +
560
       "VALUES (null, ?, ?, ?, ?, ?, sysdate, sysdate)");
561
    // Bind the values to the query and execute it
562
    pstmt.setString(1, docid);
563
    pstmt.setLong(2, rnodeid);
564
    pstmt.setString(3, docname);
565
    pstmt.setString(4, doctype);
566
    pstmt.setString(5, doctitle);
567
    //pstmt.setDate(6, date_created);
568
    pstmt.execute();
569
    pstmt.close();
570
  }
571

    
572
  /**
573
   * Set up the parser handlers for writing the document to the database
574
   */
575
  private XMLReader initializeParser(String action, String docid) {
576
    XMLReader parser = null;
577
    //
578
    // Set up the SAX document handlers for parsing
579
    //
580
    try {
581
      ContentHandler chandler   = new DBSAXHandler(conn, action, docid);
582
      EntityResolver dbresolver = new DBEntityResolver(conn, 
583
                                      (DBSAXHandler)chandler);
584
      DTDHandler dtdhandler     = new DBDTDHandler(conn);
585

    
586
      // Get an instance of the parser
587
      parser = XMLReaderFactory.createXMLReader(parserName);
588

    
589
      // Turn off validation
590
      parser.setFeature("http://xml.org/sax/features/validation", false);
591
      
592
      // Set Handlers in the parser
593
      parser.setProperty("http://xml.org/sax/properties/declaration-handler",
594
                         chandler);
595
      parser.setProperty("http://xml.org/sax/properties/lexical-handler",
596
                         chandler);
597
      parser.setContentHandler((ContentHandler)chandler);
598
      parser.setEntityResolver((EntityResolver)dbresolver);
599
      parser.setDTDHandler((DTDHandler)dtdhandler);
600
      parser.setErrorHandler((ErrorHandler)chandler);
601

    
602
    } catch (Exception e) {
603
       System.err.println(e.toString());
604
    }
605

    
606
    return parser;
607
  }
608

    
609
  /**
610
   * the main routine used to test the DBWriter utility.
611
   * <p>
612
   * Usage: java DocumentImpl <-f filename -a action -d docid>
613
   *
614
   * @param filename the filename to be loaded into the database
615
   * @param action the action to perform (READ, INSERT, UPDATE, DELETE)
616
   * @param docid the id of the document to process
617
   */
618
  static public void main(String[] args) {
619
     
620
    try {
621
      String filename = null;
622
      String action   = null;
623
      String docid    = null;
624

    
625
      for ( int i=0 ; i < args.length; ++i ) {
626
        if ( args[i].equals( "-f" ) ) {
627
          filename =  args[++i];
628
        } else if ( args[i].equals( "-a" ) ) {
629
          action =  args[++i];
630
        } else if ( args[i].equals( "-d" ) ) {
631
          docid =  args[++i];
632
        } else {
633
          System.err.println
634
            ( "   args[" +i+ "] '" +args[i]+ "' ignored." );
635
        }
636
      }
637
      
638
      boolean argsAreValid = false;
639
      if (action != null) {
640
        if (action.equals("INSERT")) {
641
          if (filename != null) {
642
            argsAreValid = true;
643
          } 
644
        } else if (action.equals("UPDATE")) {
645
          if ((filename != null) && (docid != null)) {
646
            argsAreValid = true;
647
          } 
648
        } else if (action.equals("DELETE")) {
649
          if (docid != null) {
650
            argsAreValid = true;
651
          } 
652
        } else if (action.equals("READ")) {
653
          if (docid != null) {
654
            argsAreValid = true;
655
          } 
656
        } 
657
      } 
658

    
659
      if (!argsAreValid) {
660
        System.err.println("Wrong number of arguments!!!");
661
        System.err.println(
662
              "USAGE: java DocumentImpl <-a INSERT> [-d docid] <-f filename>");
663
        System.err.println(
664
              "   OR: java DocumentImpl <-a UPDATE -d docid -f filename>");
665
        System.err.println(
666
              "   OR: java DocumentImpl <-a DELETE -d docid>");
667
        System.err.println(
668
              "   OR: java DocumentImpl <-a READ -d docid>");
669
        return;
670
      }
671
 
672
      // Open a connection to the database
673
      MetaCatUtil util = new MetaCatUtil();
674
      Connection dbconn = util.openDBConnection();
675

    
676
      if (action.equals("READ")) {
677
          DocumentImpl xmldoc = new DocumentImpl( dbconn, docid );
678
          System.out.println(xmldoc.toString());
679
      } else {
680
        DocumentImpl doc = new DocumentImpl(dbconn);
681
        if (action.equals("DELETE")) {
682
          doc.delete(docid);
683
          System.out.println("Document deleted: " + docid);
684
        } else {
685
          String newdocid = doc.write(filename, action, docid);
686
          if ((docid != null) && (!docid.equals(newdocid))) {
687
            if (action.equals("INSERT")) {
688
              System.out.println("New document ID generated!!! ");
689
            } else if (action.equals("UPDATE")) {
690
              System.out.println("ERROR: Couldn't update document!!! ");
691
            }
692
          } else if ((docid == null) && (action.equals("UPDATE"))) {
693
              System.out.println("ERROR: Couldn't update document!!! ");
694
          }
695
          System.out.println("Document processing finished for: " + filename
696
                + " (" + newdocid + ")");
697
        }
698
      }
699

    
700
    } catch (McdbException me) {
701
      me.toXml(new PrintWriter(System.err));
702
    } catch (AccessionNumberException ane) {
703
      System.out.println("ERROR: Couldn't delete document!!! ");
704
      System.out.println(ane.getMessage());
705
    } catch (Exception e) {
706
      System.err.println("EXCEPTION HANDLING REQUIRED");
707
      System.err.println(e.getMessage());
708
      e.printStackTrace(System.err);
709
    }
710
  }
711
}
(15-15/27)