Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that handles the SAX XML events as they
4
 *             are generated from XML documents
5
 *  Copyright: 2000 Regents of the University of California and the
6
 *             National Center for Ecological Analysis and Synthesis
7
 *    Authors: Matt Jones, Jivka Bojilova
8
 *    Release: @release@
9
 *
10
 *   '$Author: berkley $'
11
 *     '$Date: 2002-01-18 10:24:13 -0800 (Fri, 18 Jan 2002) $'
12
 * '$Revision: 899 $'
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.sql.*;
32
import java.io.StringReader;
33
import java.util.Stack;
34
import java.util.Vector;
35
import java.util.Hashtable;
36
import java.util.Enumeration;
37
import java.util.EmptyStackException;
38

    
39
import org.xml.sax.Attributes;
40
import org.xml.sax.SAXException;
41
import org.xml.sax.SAXParseException;
42
import org.xml.sax.ext.DeclHandler;
43
import org.xml.sax.ext.LexicalHandler;
44
import org.xml.sax.helpers.DefaultHandler;
45

    
46
/** 
47
 * A database aware Class implementing callback bethods for the SAX parser to
48
 * call when processing the XML stream and generating events
49
 */
50
public class DBSAXHandler extends DefaultHandler 
51
                          implements LexicalHandler, DeclHandler, Runnable {
52

    
53
   private boolean	atFirstElement;
54
   private boolean	processingDTD;
55
   private String 	docname = null;
56
   private String 	doctype;
57
   private String 	systemid;
58
   private boolean 	stackCreated = false;
59
   private Stack 	  nodeStack;
60
   private Vector   nodeIndex;
61
   private Connection	  conn = null;
62
   private DocumentImpl currentDocument;
63
   private DBSAXNode    rootNode;
64
   private String   action = null;
65
   private String   docid = null;
66
   private String   user = null;
67
   private String[] groups = null;
68
   private String   pub = null;
69
   private Thread   xmlIndex;
70
   private boolean endDocument = false;
71
   private int serverCode = 1;
72
   private Hashtable namespaces = new Hashtable();
73

    
74
   private static final int MAXDATACHARS = 4000;
75
// DOCTITLE attr cleared from the db
76
//   private static final int MAXTITLELEN = 1000;
77

    
78
   /** Construct an instance of the handler class 
79
    *
80
    * @param conn the JDBC connection to which information is written
81
    */
82
   public DBSAXHandler(Connection conn) {
83
     this.conn = conn;
84
     this.atFirstElement = true;
85
     this.processingDTD = false;
86

    
87
     // Create the stack for keeping track of node context
88
     // if it doesn't already exist
89
     if (!stackCreated) {
90
       nodeStack = new Stack();
91
       nodeIndex = new Vector();
92
       stackCreated = true;
93
     }
94
   }
95
   
96
   /** Construct an instance of the handler class 
97
    *
98
    * @param conn the JDBC connection to which information is written
99
    * @param action - "INSERT" or "UPDATE"
100
    * @param docid to be inserted or updated into JDBC connection
101
    * @param user the user connected to MetaCat servlet and owns the document
102
    * @param groups the groups to which user belongs
103
    * @param pub flag for public "read" access on document
104
    * @param serverCode the serverid from xml_replication on which this document
105
    *        resides.
106
    *
107
    */
108
   public DBSAXHandler(Connection conn, String action, String docid,
109
                       String user, String[] groups, String pub, int serverCode)
110
   {
111
     this(conn);
112
     this.action = action;
113
     this.docid = docid;
114
     this.user = user;
115
     this.groups = groups;
116
     this.pub = pub;
117
     this.serverCode = serverCode;
118
     this.xmlIndex = new Thread(this);
119
   }
120
 
121
   /** SAX Handler that receives notification of beginning of the document */
122
   public void startDocument() throws SAXException {
123
     MetaCatUtil.debugMessage("start Document");
124

    
125
     // Create the document node representation as root
126
     rootNode = new DBSAXNode(conn, this.docid);
127
     // Add the node to the stack, so that any text data can be 
128
     // added as it is encountered
129
     nodeStack.push(rootNode);
130
   }
131

    
132
   /** SAX Handler that receives notification of end of the document */
133
   public void endDocument() throws SAXException {
134
     MetaCatUtil.debugMessage("end Document");
135
     // Starting new thread for writing XML Index.
136
     // It calls the run method of the thread.
137
     try {
138
       xmlIndex.start();
139
     } catch (NullPointerException e) {
140
       xmlIndex = null;
141
       throw new 
142
       SAXException("Problem with starting thread for writing XML Index. " +
143
                    e.getMessage());
144
     }
145
   }
146

    
147
   /** SAX Handler that is called at the start of Namespace */
148
   public void startPrefixMapping(String prefix, String uri) 
149
                                          throws SAXException
150
   {
151
    MetaCatUtil.debugMessage("NAMESPACE");
152

    
153
    namespaces.put(prefix, uri);
154
   }
155
   
156
   /** SAX Handler that is called at the start of each XML element */
157
   public void startElement(String uri, String localName,
158
                            String qName, Attributes atts) 
159
               throws SAXException {
160
     MetaCatUtil.debugMessage("Start ELEMENT " + qName);
161
 
162
     DBSAXNode parentNode = null;
163
     DBSAXNode currentNode = null;
164

    
165
     // Get a reference to the parent node for the id
166
     try {
167
       parentNode = (DBSAXNode)nodeStack.peek();
168
     } catch (EmptyStackException e) {
169
       parentNode = null;
170
     }
171

    
172
     // Document representation that points to the root document node
173
     if (atFirstElement) {
174
       atFirstElement = false;
175
       // If no DOCTYPE declaration: docname = root element name 
176
       if (docname == null) {
177
         docname = localName;
178
         doctype = docname;
179
         MetaCatUtil.debugMessage("DOCNAME-a: " + docname);
180
         MetaCatUtil.debugMessage("DOCTYPE-a: " + doctype);
181
       } else if (doctype == null) {
182
         doctype = docname;
183
         MetaCatUtil.debugMessage("DOCTYPE-b: " + doctype);
184
       }
185
       rootNode.writeNodename(docname);
186
       try {
187
         // for validated XML Documents store a reference to XML DB Catalog
188
         String catalogid = null;
189
         if ( systemid != null ) {
190
           Statement stmt = conn.createStatement();
191
           ResultSet rs = stmt.executeQuery(
192
                          "SELECT catalog_id FROM xml_catalog " +
193
                          "WHERE entry_type = 'DTD' " + 
194
                          "AND public_id = '" + doctype + "'");
195
           boolean hasRow = rs.next();
196
           if ( hasRow ) {
197
            catalogid = rs.getString(1);
198
           }
199
           stmt.close();
200
         }
201
         currentDocument = new DocumentImpl(conn, rootNode.getNodeID(), 
202
                               docname, doctype, docid, action, user, 
203
                               this.pub, catalogid, this.serverCode);
204
       } catch (Exception ane) {
205
         throw (new SAXException("Error in DBSaxHandler.startElement " + 
206
                                 action, ane));
207
       }
208
     }      
209

    
210
     // Create the current node representation
211
     currentNode = new DBSAXNode(conn, qName, localName, parentNode,
212
                                 currentDocument.getRootNodeID(),docid,
213
                                 currentDocument.getDoctype());
214
                               
215
     // Add all of the namespaces
216
     String prefix;
217
     String nsuri;
218
     Enumeration prefixes = namespaces.keys();
219
     while ( prefixes.hasMoreElements() ) {
220
       prefix = (String)prefixes.nextElement();
221
       nsuri = (String)namespaces.get(prefix);
222
       currentNode.setNamespace(prefix, nsuri, docid);
223
     }
224
     namespaces = null;
225
     namespaces = new Hashtable();
226

    
227
     // Add all of the attributes
228
     for (int i=0; i<atts.getLength(); i++) {
229
       currentNode.setAttribute(atts.getQName(i), atts.getValue(i), docid);
230
     }      
231

    
232
     // Add the node to the stack, so that any text data can be 
233
     // added as it is encountered
234
     nodeStack.push(currentNode);
235
     // Add the node to the vector used by thread for writing XML Index
236
     nodeIndex.addElement(currentNode);
237

    
238
  }
239
  
240
  /* The run method of xmlIndex thread. It writes XML Index for the document. */
241
  public void run () {
242
    DBSAXNode currNode = null;
243
    DBSAXNode prevNode = null;
244
    Connection dbconn = null;
245
    String doctype = currentDocument.getDoctype();
246
    int step = 0;
247
    int counter = 0;
248

    
249
    try {
250
      // Opening separate db connection for writing XML Index
251
      MetaCatUtil util = new MetaCatUtil();
252
      dbconn = util.openDBConnection();
253
      dbconn.setAutoCommit(false);
254
      
255
      //the following while loop construct checks to make sure that the docid
256
      //of the document that we are trying to index is already
257
      //in the xml_documents table.  if this is not the case, the foreign
258
      //key relationship between xml_documents and xml_index is temporarily
259
      //broken causing multiple problems.
260
      boolean inxmldoc = false;
261
      while(!inxmldoc)
262
      {
263
        String xmlDocumentsCheck = "select distinct docid from xml_documents";
264
        PreparedStatement xmlDocCheck = dbconn.prepareStatement(xmlDocumentsCheck);
265
        xmlDocCheck.execute();
266
        ResultSet doccheckRS = xmlDocCheck.getResultSet();
267
        boolean tableHasRows = doccheckRS.next();
268
        Vector docids = new Vector();
269
        while(tableHasRows) 
270
        {
271
          docids.add(doccheckRS.getString(1).trim());
272
          tableHasRows = doccheckRS.next();
273
        }
274
        
275
        for(int i=0; i<docids.size(); i++)
276
        {
277
          String d = ((String)docids.elementAt(i)).trim();
278
          if(docid.trim().equals(d))
279
          {
280
            inxmldoc = true;
281
          }
282
        }
283
        xmlDocCheck.close();
284
      }
285
      
286
      // Going through the elements of the document and writing its Index
287
      Enumeration nodes = nodeIndex.elements();
288
      while ( nodes.hasMoreElements() ) {
289
        currNode = (DBSAXNode)nodes.nextElement();
290
        currNode.updateNodeIndex(dbconn, docid, doctype);
291
      }
292
    
293
      dbconn.commit();
294
         
295
      //if this is a package file
296
      String packagedoctype = util.getOption("packagedoctype");
297
      Vector packagedoctypes = new Vector();
298
      
299
      packagedoctypes = MetaCatUtil.getOptionList(packagedoctype);
300
      
301
      if ( packagedoctypes.contains(doctype) )
302
      {
303
        // write the package info to xml_relation table
304
        RelationHandler rth = new RelationHandler(docid, dbconn);
305
        // from the relations get the access file id for that package
306
        String aclid = rth.getAccessFileID(docid);
307
        // if there are access file, write ACL for that package
308
        if ( aclid != null ) {
309
          runAccessControlList(dbconn, aclid);
310
        }
311
      }
312
      // if it is an access file
313
      else if ( MetaCatUtil.getOptionList(
314
                            util.getOption("accessdoctype")).contains(doctype) )
315
      {
316
        // write ACL for the package
317
        runAccessControlList(dbconn, docid);
318
      }
319
      
320
      dbconn.close();
321

    
322
    } catch (Exception e) {
323
      try {
324
        dbconn.rollback();
325
        dbconn.close();
326
      } catch (SQLException sqle) {}
327
      System.out.println("Error in DBSAXHandler.run " + e.getMessage());
328
      e.printStackTrace();
329
    }      
330
  }
331
  
332
  // It runs in xmlIndex thread. It writes ACL for a package.
333
  private void runAccessControlList (Connection conn, String aclid)
334
                                                throws Exception
335
  {
336
    // read the access file from xml_nodes
337
    // parse the access file and store the access info into xml_access
338
    AccessControlList aclobj = 
339
    new AccessControlList(conn, aclid, //new StringReader(xml),
340
                          user, groups, serverCode);
341
    conn.commit();
342
  }
343

    
344

    
345
  /** SAX Handler that is called for each XML text node */
346
  public void characters(char[] cbuf, int start, int len) throws SAXException {
347
     MetaCatUtil.debugMessage("CHARACTERS");
348
     DBSAXNode currentNode = (DBSAXNode)nodeStack.peek();
349
     String data = null;
350
     int leftover = len;
351
     int offset = start;
352
     boolean moredata = true;
353
    
354
     // This loop deals with the case where there are more characters 
355
     // than can fit in a single database text field (limit is 
356
     // MAXDATACHARS).  If the text to be inserted exceeds MAXDATACHARS,
357
     // write a series of nodes that are MAXDATACHARS long, and then the
358
     // final node contains the remainder
359
     while (moredata) {
360
       if (leftover > MAXDATACHARS) {
361
         data = new String(cbuf, offset, MAXDATACHARS);
362
         leftover -= MAXDATACHARS;
363
         offset += MAXDATACHARS;
364
       } else {
365
         data = new String(cbuf, offset, leftover);
366
         moredata = false;
367
       }
368

    
369
       // Write the content of the node to the database
370
       currentNode.writeChildNodeToDB("TEXT", null, data, docid);
371
     }
372
   }
373

    
374
   /** 
375
    * SAX Handler that is called for each XML text node that is
376
    * Ignorable white space
377
    */
378
   public void ignorableWhitespace(char[] cbuf, int start, int len)
379
               throws SAXException {
380
     // When validation is turned "on", white spaces are reported here
381
     // When validation is turned "off" white spaces are not reported here,
382
     // but through characters() callback
383
     MetaCatUtil.debugMessage("IGNORABLEWHITESPACE");
384
     //System.out.println("Whitespace:" + len + "x" + new String(cbuf, start, len) + "x");
385

    
386
     DBSAXNode currentNode = (DBSAXNode)nodeStack.peek();
387
     String data = null;
388
     int leftover = len;
389
     int offset = start;
390
     boolean moredata = true;
391
     
392
     // This loop deals with the case where there are more characters 
393
     // than can fit in a single database text field (limit is 
394
     // MAXDATACHARS).  If the text to be inserted exceeds MAXDATACHARS,
395
     // write a series of nodes that are MAXDATACHARS long, and then the
396
     // final node contains the remainder
397
     while (moredata) {
398
       if (leftover > MAXDATACHARS) {
399
         data = new String(cbuf, offset, MAXDATACHARS);
400
         leftover -= MAXDATACHARS;
401
         offset += MAXDATACHARS;
402
       } else {
403
         data = new String(cbuf, offset, leftover);
404
         moredata = false;
405
       }
406

    
407
       // Write the content of the node to the database
408
       currentNode.writeChildNodeToDB("TEXT", null, data, docid);
409
     }
410
   }
411

    
412
   /** 
413
    * SAX Handler called once for each processing instruction found: 
414
    * node that PI may occur before or after the root element.
415
    */
416
   public void processingInstruction(String target, String data) 
417
          throws SAXException {
418
     MetaCatUtil.debugMessage("PI");
419
     DBSAXNode currentNode = (DBSAXNode)nodeStack.peek();
420
     currentNode.writeChildNodeToDB("PI", target, data, docid);
421
   }
422

    
423
   /** SAX Handler that is called at the end of each XML element */
424
   public void endElement(String uri, String localName,
425
                          String qName) throws SAXException {
426
     MetaCatUtil.debugMessage("End ELEMENT " + qName);
427

    
428
     // Get the node from the stack
429
     DBSAXNode currentNode = (DBSAXNode)nodeStack.pop();
430
   }
431

    
432
   //
433
   // the next section implements the LexicalHandler interface
434
   //
435

    
436
   /** SAX Handler that receives notification of DOCTYPE. Sets the DTD */
437
   public void startDTD(String name, String publicId, String systemId) 
438
               throws SAXException {
439
     docname = name;
440
     doctype = publicId;
441
     systemid = systemId;
442

    
443
     MetaCatUtil.debugMessage("Start DTD");
444
     MetaCatUtil.debugMessage("DOCNAME: " + docname);
445
     MetaCatUtil.debugMessage("DOCTYPE: " + doctype);
446
     MetaCatUtil.debugMessage("  SYSID: " + systemid);
447
   }
448

    
449
   /** 
450
    * SAX Handler that receives notification of end of DTD 
451
    */
452
   public void endDTD() throws SAXException {
453
    
454
     MetaCatUtil.debugMessage("end DTD");
455
   }
456

    
457
   /** 
458
    * SAX Handler that receives notification of comments in the DTD
459
    */
460
   public void comment(char[] ch, int start, int length) throws SAXException {
461
     MetaCatUtil.debugMessage("COMMENT");
462
     if ( !processingDTD ) {
463
       DBSAXNode currentNode = (DBSAXNode)nodeStack.peek();
464
       currentNode.writeChildNodeToDB("COMMENT", null, new String(ch), docid);
465
     }
466
   }
467

    
468
   /** 
469
    * SAX Handler that receives notification of the start of CDATA sections
470
    */
471
   public void startCDATA() throws SAXException {
472
     MetaCatUtil.debugMessage("start CDATA");
473
   }
474

    
475
   /** 
476
    * SAX Handler that receives notification of the end of CDATA sections
477
    */
478
   public void endCDATA() throws SAXException {
479
     MetaCatUtil.debugMessage("end CDATA");
480
   }
481

    
482
   /** 
483
    * SAX Handler that receives notification of the start of entities
484
    */
485
   public void startEntity(String name) throws SAXException {
486
     MetaCatUtil.debugMessage("start ENTITY: " + name);
487
//System.out.println("start ENTITY: " + name);
488
     if (name.equals("[dtd]")) {
489
       processingDTD = true;
490
     }
491
   }
492

    
493
   /** 
494
    * SAX Handler that receives notification of the end of entities
495
    */
496
   public void endEntity(String name) throws SAXException {
497
     MetaCatUtil.debugMessage("end ENTITY: " + name);
498
//System.out.println("end ENTITY: " + name);
499
     if (name.equals("[dtd]")) {
500
       processingDTD = false;
501
     }
502
   }
503

    
504
   /** 
505
    * SAX Handler that receives notification of element declarations
506
    */
507
   public void elementDecl(String name, String model)
508
                        throws org.xml.sax.SAXException {
509
//System.out.println("ELEMENTDECL: " + name + " " + model);
510
     MetaCatUtil.debugMessage("ELEMENTDECL: " + name + " " + model);
511
   }
512

    
513
   /** 
514
    * SAX Handler that receives notification of attribute declarations
515
    */
516
   public void attributeDecl(String eName, String aName,
517
                        String type, String valueDefault, String value)
518
                        throws org.xml.sax.SAXException {
519

    
520
//System.out.println("ATTRIBUTEDECL: " + eName + " " 
521
//                        + aName + " " + type + " " + valueDefault + " "
522
//                        + value);
523
     MetaCatUtil.debugMessage("ATTRIBUTEDECL: " + eName + " " 
524
                        + aName + " " + type + " " + valueDefault + " "
525
                        + value);
526
   }
527

    
528
   /** 
529
    * SAX Handler that receives notification of internal entity declarations
530
    */
531
   public void internalEntityDecl(String name, String value)
532
                        throws org.xml.sax.SAXException {
533
//System.out.println("INTERNENTITYDECL: " + name + " " + value);
534
     MetaCatUtil.debugMessage("INTERNENTITYDECL: " + name + " " + value);
535
   }
536

    
537
   /** 
538
    * SAX Handler that receives notification of external entity declarations
539
    */
540
   public void externalEntityDecl(String name, String publicId,
541
                        String systemId)
542
                        throws org.xml.sax.SAXException {
543
//System.out.println("EXTERNENTITYDECL: " + name + " " + publicId 
544
//                              + " " + systemId);
545
     MetaCatUtil.debugMessage("EXTERNENTITYDECL: " + name + " " + publicId 
546
                              + " " + systemId);
547
     // it processes other external entity, not the DTD;
548
     // it doesn't signal for the DTD here
549
     processingDTD = false;
550
   }
551

    
552
   //
553
   // the next section implements the ErrorHandler interface
554
   //
555

    
556
   /** 
557
    * SAX Handler that receives notification of fatal parsing errors
558
    */
559
   public void fatalError(SAXParseException exception) throws SAXException {
560
     MetaCatUtil.debugMessage("FATALERROR");
561
     throw (new SAXException("Fatal processing error.", exception));
562
   }
563

    
564
   /** 
565
    * SAX Handler that receives notification of recoverable parsing errors
566
    */
567
   public void error(SAXParseException exception) throws SAXException {
568
     MetaCatUtil.debugMessage("ERROR");
569
     throw (new SAXException("Processing error.", exception));
570
   }
571

    
572
   /** 
573
    * SAX Handler that receives notification of warnings
574
    */
575
   public void warning(SAXParseException exception) throws SAXException {
576
     MetaCatUtil.debugMessage("WARNING");
577
     throw (new SAXException("Warning.", exception));
578
   }
579

    
580
   // 
581
   // Helper, getter and setter methods
582
   //
583
   
584
   /**
585
    * get the document name
586
    */
587
   public String getDocname() {
588
     return docname;
589
   }
590

    
591
   /**
592
    * get the document processing state
593
    */
594
   public boolean processingDTD() {
595
     return processingDTD;
596
   }
597
}
(15-15/40)