Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that loads eml-access.xml file containing ACL 
4
 *             for a metadata document into relational DB
5
 *  Copyright: 2000 Regents of the University of California and the
6
 *             National Center for Ecological Analysis and Synthesis
7
 *    Authors: Jivka Bojilova
8
 *    Release: @release@
9
 *
10
 *   '$Author: bojilova $'
11
 *     '$Date: 2001-01-19 11:31:55 -0800 (Fri, 19 Jan 2001) $'
12
 * '$Revision: 682 $'
13
 *
14
 * This program is free software; you can redistribute it and/or modify
15
 * it under the terms of the GNU General Public License as published by
16
 * the Free Software Foundation; either version 2 of the License, or
17
 * (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU General Public License
25
 * along with this program; if not, write to the Free Software
26
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27
 */
28

    
29
package edu.ucsb.nceas.metacat;
30

    
31
import java.io.*;
32
import java.sql.*;
33
import java.util.Stack;
34
import java.util.Vector;
35
import java.util.Hashtable;
36
import java.net.URL;
37
import java.net.MalformedURLException;
38

    
39
import org.xml.sax.Attributes;
40
import org.xml.sax.InputSource;
41
import org.xml.sax.ContentHandler;
42
import org.xml.sax.EntityResolver;
43
import org.xml.sax.ErrorHandler;
44
import org.xml.sax.SAXException;
45
import org.xml.sax.SAXParseException;
46
import org.xml.sax.XMLReader;
47
import org.xml.sax.helpers.XMLReaderFactory;
48
import org.xml.sax.helpers.DefaultHandler;
49

    
50
/** 
51
 * A Class that loads eml-access.xml file containing ACL for a metadata
52
 * document into relational DB. It extends DefaultHandler class to handle
53
 * SAX parsing events when processing the XML stream.
54
 */
55
public class AccessControlList extends DefaultHandler {
56

    
57
  static final int ALL = 1;
58
  static final int WRITE = 2;
59
  static final int READ = 4;
60

    
61
  private Connection conn;
62
  private String parserName;
63
  private Stack elementStack;
64
  private String server;
65

    
66
  private boolean	processingDTD;
67
  private String  user;
68
  private String  group;
69
  private String  aclid;
70
  private String 	docname;
71
  private String 	doctype;
72
  private String 	systemid;
73

    
74
  private String docurl;
75
  private Vector resourceURL;
76
  private Vector resourceID;
77
  private Vector principal;
78
  private int    permission;
79
  private String permType;
80
  private String permOrder;
81
  private String publicAcc;
82
  private String beginTime;
83
  private String endTime;
84
  private int    ticketCount;
85
  
86
  /**
87
   * Construct an instance of the AccessControlList class.
88
   * It is used by the permission check up from DBQuery and DocumentImpl
89
   *
90
   * @param conn the JDBC connection where acl data are loaded
91
   */
92
  public AccessControlList ( Connection conn ) throws SQLException
93
  {
94
    this.conn = conn;
95
  }
96

    
97
  /**
98
   * Construct an instance of the AccessControlList class.
99
   * It parse acl file and loads acl data into db connection.
100
   *
101
   * @param conn the JDBC connection where acl data are loaded
102
   * @param docid the Accession# of the document with the acl data
103
   * @param acl the acl file containing acl data
104
   */
105
  public AccessControlList(Connection conn, String aclid, Reader acl,
106
                           String user, String group)
107
                  throws SAXException, IOException, ClassNotFoundException 
108
  {
109
    // Get an instance of the parser
110
    MetaCatUtil util = new MetaCatUtil();
111
    String parserName = util.getOption("saxparser");
112
    this.server = util.getOption("server");
113

    
114
    this.conn = conn;
115
    this.parserName = parserName;
116
    this.processingDTD = false;
117
    this.elementStack = new Stack();
118
    
119
    this.user = user;
120
    this.group = group;
121
    this.aclid = aclid;
122
    this.resourceURL = new Vector();
123
    this.resourceID = new Vector();
124
    this.principal = new Vector();
125
    this.permission = 0;
126
    this.ticketCount = 0;
127
    
128
    // Initialize the parser and read the queryspec
129
    XMLReader parser = initializeParser();
130
    parser.parse(new InputSource(acl));
131

    
132
  }
133

    
134
  /**
135
   * Construct an instance of the AccessControlList class.
136
   * It parse acl file and loads acl data into db connection.
137
   *
138
   * @param conn the JDBC connection where acl data are loaded
139
   * @param docid the Accession# of the document with the acl data
140
   * @param aclfilename the name of acl file containing acl data
141
   */
142
  public AccessControlList( Connection conn, String aclid, String aclfilename,
143
                           String user, String group )
144
                  throws SAXException, IOException, ClassNotFoundException 
145
  {
146
    this(conn, aclid, new FileReader(new File(aclfilename).toString()), 
147
         user, group);
148
  }
149
  
150
  /* Set up the SAX parser for reading the XML serialized ACL */
151
  private XMLReader initializeParser() throws SAXException 
152
  {
153
    XMLReader parser = null;
154

    
155
    // Get an instance of the parser
156
    parser = XMLReaderFactory.createXMLReader(parserName);
157

    
158
    // Turn off validation
159
    parser.setFeature("http://xml.org/sax/features/validation", true);
160
      
161
    // Set Handlers in the parser
162
    // Set the ContentHandler to this instance
163
    parser.setContentHandler((ContentHandler)this);
164

    
165
    // make a DBEntityResolver instance
166
    // Set the EntityReslover to DBEntityResolver instance
167
    EntityResolver eresolver = new DBEntityResolver(conn,this,null);
168
    parser.setEntityResolver((EntityResolver)eresolver);
169

    
170
    // Set the ErrorHandler to this instance
171
    parser.setErrorHandler((ErrorHandler)this);
172

    
173
    return parser;
174
  }
175
  
176
  /**
177
   * callback method used by the SAX Parser when beginning of the document
178
   */
179
  public void startDocument() throws SAXException 
180
  {
181
    //delete all previously submitted permissions @ relations
182
    //this happens only on UPDATE of the access file
183
    try {
184
      if ( aclid != null ) {
185
        //first delete all permissions for resources related to @aclid if any
186
        deletePermissionsForRelatedResources(aclid);
187
        //then delete all relations with docid of @aclid if any
188
        deleteRelations(aclid);
189
      }
190
    } catch (SQLException sqle) {
191
      throw new SAXException(sqle);
192
    }
193
  }
194
  
195
  /**
196
   * callback method used by the SAX Parser when the start tag of an 
197
   * element is detected. Used in this context to parse and store
198
   * the acl information in class variables.
199
   */
200
  public void startElement (String uri, String localName, 
201
                            String qName, Attributes atts) 
202
         throws SAXException 
203
  {
204
    BasicNode currentNode = new BasicNode(localName);
205
    if (atts != null) {
206
      int len = atts.getLength();
207
      for (int i = 0; i < len; i++) {
208
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
209
      }
210
    }
211
    if ( currentNode.getTagName().equals("resource") ) {
212
      permOrder = currentNode.getAttribute("order");
213
      publicAcc = currentNode.getAttribute("public");
214
    }
215
    elementStack.push(currentNode); 
216
  }
217

    
218
  /**
219
   * callback method used by the SAX Parser when the text sequences of an 
220
   * xml stream are detected. Used in this context to parse and store
221
   * the acl information in class variables.
222
   */
223
  public void characters(char ch[], int start, int length)
224
         throws SAXException 
225
  {
226
    String inputString = new String(ch, start, length);
227
    BasicNode currentNode = (BasicNode)elementStack.peek(); 
228
    String currentTag = currentNode.getTagName();
229

    
230
    if (currentTag.equals("resourceIdentifier")) {
231

    
232
      // docid of the current resource
233
      String docid = getDocid(inputString); 
234
      // URL string of the current resource
235
      // docurl is declared in the class
236
      try {
237
        docurl = (new URL(inputString)).toString();
238
      } catch (MalformedURLException murle) {
239
        throw new SAXException(murle.getMessage());
240
      }
241
      // collect them in Vector variables
242
      resourceID.addElement(docid);
243
      resourceURL.addElement(docurl);
244
      // check permissions for @user on the current resource first
245
      // @user must have permission "all" on it(docid)
246
      boolean hasPermission = false;
247
      try {
248
        hasPermission = hasPermission("ALL",user,docid);
249
        if ( !hasPermission && group != null ) {
250
          hasPermission = hasPermission("ALL",group,docid);
251
        }
252
      } catch (SQLException e) {
253
        throw new SAXException(e.getMessage());
254
      }
255
      if ( !hasPermission ) {
256
        throw new SAXException(
257
         "Permission denied for setting access control on " + docid);
258
      }
259
      // end of check for "all" perm on docid
260

    
261
    } else if (currentTag.equals("principal")) {
262

    
263
      principal.addElement(inputString);
264

    
265
    } else if (currentTag.equals("permission")) {
266

    
267
      if ( inputString.trim().toUpperCase().equals("READ") ) {
268
        permission = permission | READ;
269
      } else if ( inputString.trim().toUpperCase().equals("WRITE") ) {
270
        permission = permission | WRITE;
271
      } else if ( inputString.trim().toUpperCase().equals("ALL") ) {
272
        permission = permission | ALL;
273
      } else {
274
        throw new SAXException("Unknown permission type: " + inputString);
275
      }
276

    
277
    } else if (currentTag.equals("duration") && 
278
               beginTime == null && endTime == null ) {
279
      try {
280
        beginTime = inputString.substring(0, inputString.indexOf(" "));
281
        endTime = inputString.substring(inputString.indexOf(" ")+1);
282
      } catch (StringIndexOutOfBoundsException se) {
283
        beginTime = inputString;
284
      }
285

    
286
    } else if (currentTag.equals("ticketCount") && ticketCount == 0 ) {
287
      try {
288
        ticketCount = (new Integer(inputString.trim())).intValue();
289
      } catch (NumberFormatException nfe) {
290
        throw new SAXException("Wrong integer format for:" + inputString);
291
      }
292
    }
293
  }
294

    
295
  /**
296
   * callback method used by the SAX Parser when the end tag of an 
297
   * element is detected. Used in this context to parse and store
298
   * the acl information in class variables.
299
   */
300
  public void endElement (String uri, String localName, String qName)
301
         throws SAXException 
302
  {
303
    BasicNode leaving = (BasicNode)elementStack.pop(); 
304
    if ( leaving.getTagName().equals("resourceIdentifier") ) {
305
      
306
      try {
307
        // make a relationship for @aclid on the current resource(docurl)
308
        if ( aclid != null ) {
309
          insertRelation(aclid, docurl);
310
        }
311
      } catch (SQLException sqle) {
312
        throw new SAXException(sqle);
313
      }
314
      
315
    } else if ( leaving.getTagName().equals("allow") ) {
316
      
317
      if ( permission > 0 ) {
318

    
319
        // insert into db calculated permission for the list of principals
320
        try {
321
          insertPermissions("allowed");
322
        } catch (SQLException sqle) {
323
          throw new SAXException(sqle);
324
        }
325
      }
326

    
327
      // reset the allowed permission
328
      principal = new Vector();
329
      permission = 0;
330
      beginTime = null;
331
      endTime = null;
332
      ticketCount = 0;
333
    
334
    } else if ( leaving.getTagName().equals("deny") ) {
335
      
336
      if ( permission > 0 ) {
337

    
338
        // insert into db calculated permission for the list of principals
339
        try {
340
          insertPermissions("denied");
341
        } catch (SQLException sqle) {
342
          throw new SAXException(sqle);
343
        }
344
      }
345

    
346
      // reset the denied permission
347
      principal = new Vector();
348
      permission = 0;
349
      beginTime = null;
350
      endTime = null;
351
      ticketCount = 0;
352

    
353
    } else if ( leaving.getTagName().equals("resource") ) {
354

    
355
      // update public access for the list of resources
356
      try {
357
        updatePublicAccess(publicAcc);
358
      } catch (SQLException sqle) {
359
        throw new SAXException(sqle);
360
      }
361
      
362
      // reset the resource
363
      resourceID = new Vector();
364
      resourceURL = new Vector();
365
      permOrder = null;
366
      publicAcc = null;
367
    }
368

    
369
  }
370

    
371
  /** SAX Handler that receives notification of DOCTYPE. Sets the DTD */
372
  public void startDTD(String name, String publicId, String systemId) 
373
              throws SAXException {
374
    docname = name;
375
    doctype = publicId;
376
    systemid = systemId;
377
//    processingDTD = true;
378
  }
379

    
380
  /** 
381
   * SAX Handler that receives notification of the start of entities
382
   */
383
  public void startEntity(String name) throws SAXException {
384
    if (name.equals("[dtd]")) {
385
      processingDTD = true;
386
    }
387
  }
388

    
389
  /** 
390
   * SAX Handler that receives notification of the end of entities
391
   */
392
  public void endEntity(String name) throws SAXException {
393
    if (name.equals("[dtd]")) {
394
      processingDTD = false;
395
    }
396
  }
397

    
398
  /**
399
   * get the document name
400
   */
401
  public String getDocname() {
402
    return docname;
403
  }
404

    
405
  /**
406
   * get the document processing state
407
   */
408
  public boolean processingDTD() {
409
    return processingDTD;
410
  }
411
  
412
  /** Delete from db all permission for resources related to @aclid if any */
413
  private void deletePermissionsForRelatedResources(String aclid) 
414
          throws SQLException 
415
  {
416
    // delete all acl records for resources related to @aclid if any
417
    Statement stmt = conn.createStatement();
418
    stmt.execute("DELETE FROM xml_access WHERE accessfileid = '" + aclid + "'");
419
    stmt.close();
420
  }
421

    
422
  /**
423
   * Deletes all of the relations with a docid of @docid.
424
   * @param docid the docid to delete.
425
   */
426
  public void deleteRelations(String docid)
427
          throws SQLException 
428
  {
429
    Statement stmt = conn.createStatement();
430
    stmt.execute("DELETE FROM xml_relation WHERE docid='" + docid + "'");
431
    stmt.close();
432
  }
433

    
434
  /** Insert relationship into db for @aclid on @resourceURL  */
435
  private void insertRelation(String aclid, String resourceURL) 
436
          throws SQLException 
437
  {
438
    String aclURL = "metacat://" + server + "/?docid=" + aclid;
439
    
440
    // insert relationship 
441
    PreparedStatement pstmt;
442
    pstmt = conn.prepareStatement(
443
            "INSERT INTO xml_relation (docid,subject,relationship,object) " +
444
            "VALUES (?, ?, ?, ?)");
445
    pstmt.setString(1, aclid);
446
    pstmt.setString(2, aclURL);
447
    pstmt.setString(3, "isaclfor");
448
    pstmt.setString(4, resourceURL);
449

    
450
    pstmt.execute();
451
    pstmt.close();
452
  }
453

    
454
  /** Insert into db calculated permission for the list of principals */
455
  private void insertPermissions( String permType ) 
456
          throws SQLException 
457
  {
458
    PreparedStatement pstmt;
459
 
460
    try {
461
      pstmt = conn.prepareStatement(
462
              "INSERT INTO xml_access " + 
463
              "(docid, principal_name, permission, perm_type, perm_order," +
464
              "begin_time,end_time,ticket_count, accessfileid) VALUES " +
465
              "(?,?,?,?,?,to_date(?,'mm/dd/yy'),to_date(?,'mm/dd/yy'),?,?)");
466
      // Bind the values to the query
467
      pstmt.setInt(3, permission);
468
      pstmt.setString(4, permType);
469
      pstmt.setString(5, permOrder);
470
      pstmt.setString(6, beginTime);
471
      pstmt.setString(7, endTime);
472
      pstmt.setString(9, aclid);
473
      if ( ticketCount > 0 ) {
474
        pstmt.setString(8, "" + ticketCount);
475
      } else {
476
        pstmt.setString(8, "");
477
      }
478
      for ( int i = 0; i < resourceID.size(); i++ ) {
479
        pstmt.setString(1, (String)resourceID.elementAt(i));
480
        for ( int j = 0; j < principal.size(); j++ ) {
481
          pstmt.setString(2, (String)principal.elementAt(j));
482
          pstmt.execute();
483
        }
484
      }
485
      pstmt.close();
486

    
487
    } catch (SQLException e) {
488
      throw new 
489
      SQLException("AccessControlList.insertPermissions(): " + e.getMessage());
490
    }
491
  }
492

    
493
  /** Update into db public read access for the list of resources */
494
  private void updatePublicAccess(String publicAcc) 
495
          throws SQLException 
496
  {
497
    try {
498
      PreparedStatement pstmt;
499
      pstmt = conn.prepareStatement(
500
              "UPDATE xml_documents SET public_access = ?" +
501
              " WHERE docid LIKE ?");
502
      // Bind the values to the query
503
      if ( publicAcc == null ) {
504
        pstmt.setString(1, null);
505
      } else if ( publicAcc.toUpperCase().equals("YES") ) {
506
        pstmt.setInt(1, 1);
507
      } else {
508
        pstmt.setInt(1, 0);
509
      }
510
      for ( int i = 0; i < resourceID.size(); i++ ) {
511
        pstmt.setString(2, (String)resourceID.elementAt(i));
512
        pstmt.execute();
513
      }
514
      pstmt.close();
515

    
516
    } catch (SQLException e) {
517
      throw new 
518
      SQLException("AccessControlList.updatePublicAccess(): " + e.getMessage());
519
    }
520
  }
521

    
522
  /** Check for @permission for @principal on @resourceID from db connection */
523
  public boolean hasPermission ( String permission,
524
                                 String principal, String resourceID )
525
                 throws SQLException
526
  {
527
    PreparedStatement pstmt;
528
    // check public access to @resourceID from xml_documents table
529
    if ( permission.equals("READ") ) {
530
      try {
531
        pstmt = conn.prepareStatement(
532
                "SELECT 'x' FROM xml_documents " +
533
                "WHERE docid LIKE ? AND public_access = 1");
534
        // Bind the values to the query
535
        pstmt.setString(1, resourceID);
536

    
537
        pstmt.execute();
538
        ResultSet rs = pstmt.getResultSet();
539
        boolean hasRow = rs.next();
540
        pstmt.close();
541
        if (hasRow) {
542
          return true;
543
        }
544
//System.out.println("Passed the check for public access");      
545

    
546
      } catch (SQLException e) {
547
        throw new 
548
        SQLException("AccessControlList.hasPermission(): " +
549
                     "Error checking document's public access. " +
550
                      e.getMessage());
551
      }
552
    }
553
    
554
    // since owner of resource has all permission on it,
555
    // check if @principal is owner of @resourceID in xml_documents table
556
    if ( principal != null ) {
557
      try {
558
        pstmt = conn.prepareStatement(
559
                "SELECT 'x' FROM xml_documents " +
560
                "WHERE docid LIKE ? AND user_owner LIKE ?");
561
        // Bind the values to the query
562
        pstmt.setString(1, resourceID);
563
        pstmt.setString(2, principal);
564

    
565
        pstmt.execute();
566
        ResultSet rs = pstmt.getResultSet();
567
        boolean hasRow = rs.next();
568
        pstmt.close();
569
        if (hasRow) {
570
          return true;
571
        }
572
//System.out.println("Passed the check for ownership");      
573
     
574
      } catch (SQLException e) {
575
        throw new 
576
        SQLException("AccessControlList.hasPermission(): " +
577
                     "Error checking document's ownership. " + e.getMessage());
578
      }
579

    
580
      // check @principal's @permission on @resourceID from xml_access table
581
      int accessValue = 0;
582
      int ticketCount = 0;
583
      String permOrder = "";
584
      try {
585
        pstmt = conn.prepareStatement(
586
                "SELECT permission, perm_order, ticket_count " +
587
                "FROM xml_access " +
588
                "WHERE docid LIKE ? " + 
589
                "AND principal_name LIKE ? " +
590
                "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
591
                                "AND nvl(end_time,sysdate) " +
592
                "AND perm_type LIKE ?");
593
        // check if it is "denied" first
594
        // Bind the values to the query
595
        pstmt.setString(1, resourceID);
596
        pstmt.setString(2, principal);
597
        pstmt.setString(3, "denied");
598

    
599
        pstmt.execute();
600
        ResultSet rs = pstmt.getResultSet();
601
        boolean hasRows = rs.next();
602
        while ( hasRows ) {
603
          accessValue = rs.getInt(1);
604
          permOrder = rs.getString(2);
605
          ticketCount = rs.getInt(3);
606
          if ( ( accessValue & intValue(permission) ) == intValue(permission) &&
607
               ( permOrder.equals("allowFirst") ) &&
608
               ( rs.wasNull() || ticketCount > 0 ) ) {
609
            if ( !rs.wasNull() && ticketCount > 0 ) {
610
              decreaseNumberOfAccess(accessValue,principal,resourceID,"denied");
611
            }
612
            pstmt.close();
613
            return false;
614
          }
615
          hasRows = rs.next();
616
        }
617
//System.out.println("Passed the check for denied access");      
618

    
619
        // it is not denied then check if it is "allowed"
620
        // Bind the values to the query
621
        pstmt.setString(1, resourceID);
622
        pstmt.setString(2, principal);
623
        pstmt.setString(3, "allowed");
624

    
625
        pstmt.execute();
626
        rs = pstmt.getResultSet();
627
        hasRows = rs.next();
628
        while ( hasRows ) {
629
          accessValue = rs.getInt(1);
630
          ticketCount = rs.getInt(3);
631
          if ( ( accessValue & intValue(permission) )==intValue(permission) &&
632
               ( rs.wasNull() || ticketCount > 0 ) ) {
633
            if ( !rs.wasNull() && ticketCount > 0 ) {
634
              decreaseNumberOfAccess(accessValue,principal,resourceID,"allowed");
635
            }
636
            pstmt.close();
637
            return true;
638
          }
639
          hasRows = rs.next();
640
        }
641
//System.out.println("Passed the check for allowed access");      
642

    
643
        // ???
644
        // here there could be a check for the group's permission like 
645
        // selfrecursive call to hasPermission(conn,permission,group,recourceId)
646
        //
647
      
648
        pstmt.close();
649
        return false;
650
  
651
      } catch (SQLException e) {
652
        throw new 
653
        SQLException("AccessControlList.hasPermission(): " +
654
                     "Error checking document's permission. " + e.getMessage());
655
      }
656
    }
657
    
658
    return false;
659
  }
660

    
661
  /** decrease the number of access to @resourceID for @principal */
662
  private void decreaseNumberOfAccess(int permission, String principal,
663
                                      String resourceID, String permType) 
664
               throws SQLException
665
  {
666
    PreparedStatement pstmt;
667
    pstmt = conn.prepareStatement(
668
            "UPDATE xml_access SET ticket_count = ticket_count - 1 " +
669
            "WHERE docid LIKE ? " +
670
            "AND principal_name LIKE ? " +
671
            "AND permission LIKE ? " +
672
            "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
673
                            "AND nvl(end_time,sysdate) " +
674
            "AND perm_type LIKE ?");
675
    // Bind the values to the query
676
    pstmt.setString(1, resourceID);
677
    pstmt.setString(2, principal);
678
    pstmt.setInt(3, permission);
679
    pstmt.setString(4, permType);
680

    
681
    pstmt.execute();
682
    pstmt.close();
683
  }
684
 
685
  // get the int value of READ, WRITE or ALL
686
  private int intValue ( String permission )
687
  {
688
    if ( permission.equals("READ") ) {
689
      return READ;
690
    } else if ( permission.equals("WRITE") ) {
691
      return WRITE;
692
    } else if ( permission.equals("ALL") ) {
693
      return ALL;
694
    }
695
    
696
    return -1;
697
  }
698
  
699
  // get docid from @url
700
  private String getDocid ( String url ) throws SAXException
701
  {
702
    MetaCatUtil util = new MetaCatUtil();
703
    try {
704
      URL urlobj = new URL(url);
705
      Hashtable urlParams = util.parseQuery(urlobj.getQuery());
706
      if ( urlParams.containsKey("docid") ) {
707
        return (String)urlParams.get("docid"); // return the docid value
708
      } else {
709
        throw new 
710
        SAXException("\"docid\" not specified within " + url);
711
      }
712
    } catch (MalformedURLException e) {
713
      throw new SAXException(e.getMessage());
714
    }
715
  }  
716

    
717
}
(1-1/43)