Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that implements a metadata catalog as a java Servlet
4
 *  Copyright: 2000 Regents of the University of California and the
5
 *             National Center for Ecological Analysis and Synthesis
6
 *    Authors: Matt Jones, Dan Higgins
7
 *
8
 *   '$Author: bojilova $'
9
 *     '$Date: 2000-06-27 14:25:08 -0700 (Tue, 27 Jun 2000) $'
10
 * '$Revision: 210 $'
11
 */
12

    
13
package edu.ucsb.nceas.metacat;
14

    
15
import java.io.PrintWriter;
16
import java.io.IOException;
17
import java.io.Reader;
18
import java.io.StringReader;
19
import java.io.BufferedReader;
20
import java.io.File;
21
import java.io.FileInputStream;
22
import java.util.Enumeration;
23
import java.util.Hashtable;
24
import java.util.ResourceBundle;
25
import java.util.PropertyResourceBundle;
26
import java.net.URL;
27
import java.net.MalformedURLException;
28
import java.sql.PreparedStatement;
29
import java.sql.ResultSet;
30
import java.sql.Connection;
31
import java.sql.SQLException;
32

    
33
import javax.servlet.ServletConfig;
34
import javax.servlet.ServletContext;
35
import javax.servlet.ServletException;
36
import javax.servlet.ServletInputStream;
37
import javax.servlet.http.HttpServlet;
38
import javax.servlet.http.HttpServletRequest;
39
import javax.servlet.http.HttpServletResponse;
40
import javax.servlet.http.HttpSession;
41
import javax.servlet.http.HttpUtils;
42

    
43
import oracle.xml.parser.v2.XSLStylesheet;
44
import oracle.xml.parser.v2.XSLException;
45
import oracle.xml.parser.v2.XMLDocumentFragment;
46
import oracle.xml.parser.v2.XSLProcessor;
47

    
48
import org.xml.sax.SAXException;
49

    
50
/**
51
 * A metadata catalog server implemented as a Java Servlet
52
 *
53
 * <p>Valid parameters are:<br>
54
 * action=query -- query the values of all elements and attributes
55
 *                     and return a result set of nodes<br>
56
 * action=squery -- structured query (see pathquery.dtd)<br>
57
 * action=insert -- insert an XML document into the database store<br>
58
 * action=update -- update an XML document that is in the database store<br>
59
 * action=delete --  delete an XML document from the database store<br>
60
 * action=validate -- vallidate the xml contained in valtext<br>
61
 * action=getdocument -- display an XML document in XML or HTML<br>
62
 * doctype -- document type list returned by the query (publicID)<br>
63
 * qformat=xml -- display resultset from query in XML<br>
64
 * qformat=html -- display resultset from query in HTML<br>
65
 * docid=34 -- display the document with the document ID number 34<br>
66
 * doctext -- XML text of the document to load into the database<br>
67
 * query -- actual query text (to go with 'action=query' or 'action=squery')<br>
68
 * valtext -- XML text to be validated<br>
69
 * action=getdatadoc -- retreive a stored datadocument<br>
70
 * datadoc -- data document name (id)<br>
71
 * <p>
72
 * The particular combination of parameters that are valid for each 
73
 * particular action value is quite specific.  This documentation
74
 * will be reorganized to reflect this information.
75
 */
76
public class MetaCatServlet extends HttpServlet {
77

    
78
  private ServletConfig		config = null;
79
  private ServletContext	context = null;
80
  private Connection 		conn = null;
81
  private DBQuery		queryobj = null;
82
  private DBReader		docreader = null;
83
  private DBTransform		dbt = null;
84
  private String 		resultStyleURL = null;
85
  private String 		xmlcatalogfile = null;
86
  private String 		saxparser = null;
87
  private String    		defaultdatapath = null; 
88
					// path to directory where data files 
89
					// that can be downloaded will be stored
90
  private String    		executescript  = null;  
91
					// script to get data file and put it 
92
                                    	// in defaultdocpath dir
93
  private PropertyResourceBundle options = null;
94

    
95
  private MetaCatUtil util = null;
96

    
97
  /**
98
   * Initialize the servlet by creating appropriate database connections
99
   */
100
  public void init( ServletConfig config ) throws ServletException {
101
    try {
102
      super.init( config );
103
      this.config = config;
104
      this.context = config.getServletContext();
105
      System.out.println("MetaCatServlet Initialize");
106

    
107
      util = new MetaCatUtil();
108

    
109
      // Get the configuration file information
110
      resultStyleURL = util.getOption("resultStyleURL");
111
      xmlcatalogfile = util.getOption("xmlcatalogfile");
112
      saxparser = util.getOption("saxparser");
113
      defaultdatapath = util.getOption("defaultdatapath");
114
      executescript = util.getOption("executescript");
115

    
116
      try {
117
        // Open a connection to the database
118
        conn = util.openDBConnection();
119

    
120
        queryobj = new DBQuery(conn,saxparser);
121
        docreader = new DBReader(conn);
122
        dbt = new DBTransform(conn);
123

    
124
      } catch (Exception e) {
125
        System.err.println("Error opening database connection");
126
      }
127
    } catch ( ServletException ex ) {
128
      throw ex;
129
    }
130
  }
131

    
132
  /** Handle "GET" method requests from HTTP clients */
133
  public void doGet (HttpServletRequest request, HttpServletResponse response)
134
    throws ServletException, IOException {
135

    
136
    // Process the data and send back the response
137
    handleGetOrPost(request, response);
138
  }
139

    
140
  /** Handle "POST" method requests from HTTP clients */
141
  public void doPost( HttpServletRequest request, HttpServletResponse response)
142
    throws ServletException, IOException {
143

    
144
    // Process the data and send back the response
145
    handleGetOrPost(request, response);
146
  }
147

    
148
  /**
149
   * Control servlet response depending on the action parameter specified
150
   */
151
  private void handleGetOrPost(HttpServletRequest request, 
152
    HttpServletResponse response) 
153
    throws ServletException, IOException {
154

    
155
    if (conn == null) {
156
      System.err.println("Connection to database lost.  Reopening...");
157
      try {
158
        // Open a connection to the database
159
        conn = util.openDBConnection();
160
  
161
        queryobj = new DBQuery(conn, saxparser);
162
        docreader = new DBReader(conn);
163
        dbt = new DBTransform(conn);
164
  
165
      } catch (Exception e) {
166
        System.err.println("Error opening database connection");
167
      }
168
    }
169

    
170
    // Get a handle to the output stream back to the client
171
    PrintWriter out = response.getWriter();
172
    //response.setContentType("text/html");
173
  
174
    String name = null;
175
    String[] value = null;
176
    String[] docid = new String[3];
177
    Hashtable params = new Hashtable();
178
    Enumeration paramlist = request.getParameterNames();
179
    while (paramlist.hasMoreElements()) {
180
      name = (String)paramlist.nextElement();
181
      value = request.getParameterValues(name);
182

    
183
      // Decode the docid and mouse click information
184
      if (name.endsWith(".y")) {
185
        docid[0] = name.substring(0,name.length()-2);
186
        //out.println("docid => " + docid[0]);
187
        params.put("docid", docid);
188
        name = "ypos";
189
      }
190
      if (name.endsWith(".x")) {
191
        name = "xpos";
192
      }
193

    
194
      //out.println(name + " => " + value[0]);
195
      params.put(name,value);
196
    }
197

    
198
    // Determine what type of request the user made
199
    // if the action parameter is set, use it as a default
200
    // but if the ypos param is set, calculate the action needed
201
    String action = ((String[])params.get("action"))[0];
202
    long ypos = 0;
203
    try {
204
      ypos = (new Long(((String[])params.get("ypos"))[0]).longValue());
205
      //out.println("<P>YPOS IS " + ypos);
206
      if (ypos <= 13) {
207
        action = "getdocument";
208
      } else if (ypos > 13 && ypos <= 27) {
209
        action = "validate";
210
      } else if (ypos > 27) {
211
        action = "transform";
212
      } else {
213
        action = "";
214
      }
215
    } catch (Exception npe) {
216
      //out.println("<P>Caught exception looking for Y value.");
217
    }
218
// Jivka added
219
    // handle login action
220
    if (action.equals("Login")) {
221
      handleLoginAction(out, params, request, response);
222
    // handle logout action  
223
    } else if (action.equals("Logout")) {
224
      HttpSession sess = request.getSession(false);
225
      if (sess != null) {
226
        sess.invalidate();
227
        response.sendRedirect("/xmltodb/lib/login.html"); 
228
      }
229
    // aware of session expiration on every request  
230
    } else {    
231
      HttpSession sess = request.getSession(false);
232
      if (sess == null) { 
233
        out.println("Session expired. You will be redirected to Login page again.");
234
        response.sendRedirect("/xmltodb/lib/login.html"); 
235
      }
236
    }    
237
// End of Jivka added
238
    if (action.equals("query") || action.equals("squery")) {
239
      handleQueryAction(out, params, response);
240
    } else if (action.equals("getdocument")) {
241
      try {
242
        handleGetDocumentAction(out, params, response);
243
      } catch (ClassNotFoundException e) {
244
        out.println(e.getMessage());
245
      } catch (SQLException se) {
246
        out.println(se.getMessage());
247
      }
248
    } else if (action.equals("insert") || action.equals("update")) {
249
      handleInsertOrUpdateAction(out, params, response);
250
    } else if (action.equals("delete")) {
251
      handleDeleteAction(out, params, response);
252
    } else if (action.equals("validate")) {
253
      handleValidateAction(out, params, response);  
254
    } else if (action.equals("getdatadoc")) {
255
      handleGetDataDocumentAction(out, params, response);  
256
    } else {
257
      out.println("Error: action not registered.  Please report this error.");
258
    }
259

    
260
    // Close the stream to the client
261
    out.close();
262
  }
263

    
264
// Jivka added
265
  /** 
266
   * Handle the Login request. Create a new session object.
267
   * Make a user authentication through SRB RMI Connection.
268
   */
269
  private void handleLoginAction(PrintWriter out, Hashtable params, 
270
               HttpServletRequest request, HttpServletResponse response) {
271
    String un = (String)params.get("username");
272
    String pw = (String)params.get("password");
273
    
274
    MetaCatSession sess = new MetaCatSession(request, un, pw);
275
    try {
276
        if (sess.userAuth(pw)) {
277
            try {
278
                response.sendRedirect("/xmltodb/lib/index.html");
279
            } catch ( java.io.IOException ioe) {
280
                sess.disconnect();            
281
                out.println("MetaCatServlet.handleLoginAction() - " +
282
                            "Error on redirect of HttpServletResponse: " + 
283
                            ioe.getMessage());
284
            }                
285
                
286
        } else {
287
            sess.disconnect();            
288
            out.println("SRB Connection failed. " +
289
                        "SRB RMI Server is not running now or " +
290
                        "user " + un + 
291
                        " has not been authenticated to use the system");
292
        }    
293
    } catch ( java.rmi.RemoteException re) {
294
            sess.disconnect();            
295
            out.println("SRB Connection failed. " + re.getMessage());
296
    }        
297
  }
298
  /** 
299
   * Handle the database query request and return a result set, possibly
300
   * transformed from XML into HTML
301
   */
302
  private void handleQueryAction(PrintWriter out, Hashtable params, 
303
               HttpServletResponse response) {
304
      String action = ((String[])params.get("action"))[0];
305
      String query = ((String[])params.get("query"))[0]; 
306
      Hashtable doclist = null;
307
      String[] doctypeArr = null;
308
      String doctype = null;
309
      Reader xmlquery = null;
310

    
311
      // Run the query if it is a structured query
312
      // or, if it is a free-text, simple query,
313
      // format it first as a structured query and then run it
314
      if (action.equals("query")) {
315
        doctypeArr = (String[])params.get("doctype");
316
        doctype = null;
317
        if (doctypeArr != null) {
318
          doctype = ((String[])params.get("doctype"))[0]; 
319
        }
320

    
321
        if (doctype != null) {
322
          xmlquery = new StringReader(DBQuery.createQuery(query,doctype));
323
        } else {
324
          xmlquery = new StringReader(DBQuery.createQuery(query));
325
        }
326
      } else if (action.equals("squery")) {
327
        xmlquery = new StringReader(query);
328
      } else {
329
        System.err.println("Error handling query -- illegal action value");
330
      }
331

    
332
      if (queryobj != null) {
333
          doclist = queryobj.findDocuments(xmlquery);
334
      } else {
335
        out.println("Query Object Init failed.");
336
        return;
337
      }
338
 
339
      // Create a buffer to hold the xml result
340
      StringBuffer resultset = new StringBuffer();
341
 
342
      // Print the resulting root nodes
343
      String docid;
344
      String document = null;
345
      resultset.append("<?xml version=\"1.0\"?>\n");
346
      //resultset.append("<!DOCTYPE resultset PUBLIC " +
347
      //               "\"-//NCEAS//resultset//EN\" \"resultset.dtd\">\n");
348
      resultset.append("<resultset>\n");
349
      resultset.append("  <query>" + query + "</query>");
350
      Enumeration doclistkeys = doclist.keys(); 
351
      while (doclistkeys.hasMoreElements()) {
352
        docid = (String)doclistkeys.nextElement();
353
        document = (String)doclist.get(docid);
354
        resultset.append("  <document>" + document + "</document>");
355
      }
356
      resultset.append("</resultset>");
357

    
358
      String qformat = ((String[])params.get("qformat"))[0]; 
359
      if (qformat.equals("xml")) {
360
        // set content type and other response header fields first
361
        response.setContentType("text/xml");
362
        out.println(resultset.toString());
363
      } else if (qformat.equals("html")) {
364
        // set content type and other response header fields first
365
        response.setContentType("text/html");
366
        //out.println("Converting to HTML...");
367
        XMLDocumentFragment htmldoc = null;
368
        try {
369
          XSLStylesheet style = new XSLStylesheet(
370
                                    new URL(resultStyleURL), null);
371
          htmldoc = (new XSLProcessor()).processXSL(style, 
372
                     (Reader)(new StringReader(resultset.toString())),null);
373
          htmldoc.print(out);
374
        } catch (Exception e) {
375
          out.println("Error transforming document:\n" + e.getMessage());
376
        }
377
      }
378
  }
379

    
380
  /** 
381
   * Handle the database getdocument request and return a XML document, 
382
   * possibly transformed from XML into HTML
383
   */
384
  private void handleGetDocumentAction(PrintWriter out, Hashtable params, 
385
               HttpServletResponse response) 
386
               throws ClassNotFoundException, IOException, SQLException {
387
    String docidstr = null;
388
    String docid = null;
389
    String doc = null;
390
    try {
391
      // Find the document id number
392
      docidstr = ((String[])params.get("docid"))[0]; 
393
      //docid = (new Long(docidstr)).longValue();
394
      docid = docidstr;
395

    
396
      // Get the document indicated fromthe db
397
      doc = docreader.readXMLDocument(docid);
398
    } catch (NullPointerException npe) {
399
      response.setContentType("text/html");
400
      out.println("Error getting document ID: " + docidstr +" (" + docid + ")");
401
    }
402

    
403
      // Return the document in XML or HTML format
404
      String qformat = ((String[])params.get("qformat"))[0]; 
405
      if (qformat.equals("xml")) {
406
        // set content type and other response header fields first
407
        response.setContentType("text/xml");
408
        out.println(doc);
409
      } else if (qformat.equals("html")) {
410
        // set content type and other response header fields first
411
        response.setContentType("text/html");
412

    
413
        // Look up the document type
414
        String sourcetype = docreader.getDoctypeInfo(docid).getDoctype();
415

    
416
        // Transform the document to the new doctype
417
        dbt.transformXMLDocument(doc, sourcetype, "-//W3C//HTML//EN", out);
418
      }
419
  }
420

    
421
  /** 
422
   * Handle the database putdocument request and write an XML document 
423
   * to the database connection
424
   */
425
  private void handleInsertOrUpdateAction(PrintWriter out, Hashtable params, 
426
               HttpServletResponse response) {
427

    
428
    try {
429
      // Get the document indicated
430
      String[] doctext = (String[])params.get("doctext");
431
      StringReader xml = null;
432
      try {
433
        xml = new StringReader(doctext[0]);
434

    
435
        String[] action = (String[])params.get("action");
436
        String[] docid = (String[])params.get("docid");
437
        String newdocid = null;
438

    
439
        String doAction = null;
440
        if (action[0].equals("insert")) {
441
          doAction = "INSERT";
442
        } else if (action[0].equals("update")) {
443
          doAction = "UPDATE";
444
        }
445

    
446
        // write the document to the database
447
        DBWriter dbw = new DBWriter(conn, saxparser);
448

    
449
        try {
450
          String accNumber = docid[0];
451
          if (accNumber.equals("")) {
452
            accNumber = null;
453
          }
454
          newdocid = dbw.write(xml, doAction, accNumber);  
455
        } catch (NullPointerException npe) {
456
          newdocid = dbw.write(xml, doAction, null);  
457
        }
458

    
459
        // set content type and other response header fields first
460
        response.setContentType("text/xml");
461
        out.println("<?xml version=\"1.0\"?>");
462
        out.println("<success>");
463
        out.println("<docid>" + newdocid + "</docid>"); 
464
        out.println("</success>");
465

    
466
      } catch (NullPointerException npe) {
467
        response.setContentType("text/xml");
468
        out.println("<?xml version=\"1.0\"?>");
469
        out.println("<error>");
470
        out.println(npe.getMessage()); 
471
        out.println("</error>");
472
      }
473
    } catch (Exception e) {
474
      response.setContentType("text/xml");
475
      out.println("<?xml version=\"1.0\"?>");
476
      out.println("<error>");
477
      out.println(e.getMessage()); 
478
      if (e instanceof SAXException) {
479
        Exception e2 = ((SAXException)e).getException();
480
        out.println("<error>");
481
        out.println(e2.getMessage()); 
482
        out.println("</error>");
483
      }
484
      //e.printStackTrace(out);
485
      out.println("</error>");
486
    }
487
  }
488

    
489
  /** 
490
   * Handle the database delete request and delete an XML document 
491
   * from the database connection
492
   */
493
  private void handleDeleteAction(PrintWriter out, Hashtable params, 
494
               HttpServletResponse response) {
495

    
496
    String[] docid = (String[])params.get("docid");
497

    
498
    // delete the document from the database
499
    try {
500
      DBWriter dbw = new DBWriter(conn, saxparser);
501
                                      // NOTE -- NEED TO TEST HERE
502
                                      // FOR EXISTENCE OF PARAM
503
                                      // BEFORE ACCESSING ARRAY
504
      try {
505
        dbw.delete(docid[0]);
506
        response.setContentType("text/xml");
507
        out.println("<?xml version=\"1.0\"?>");
508
        out.println("<success>");
509
        out.println("Document deleted."); 
510
        out.println("</success>");
511
      } catch (AccessionNumberException ane) {
512
        response.setContentType("text/xml");
513
        out.println("<?xml version=\"1.0\"?>");
514
        out.println("<error>");
515
        out.println("Error deleting document!!!");
516
        out.println(ane.getMessage()); 
517
        out.println("</error>");
518
      }
519
    } catch (Exception e) {
520
      response.setContentType("text/xml");
521
      out.println("<?xml version=\"1.0\"?>");
522
      out.println("<error>");
523
      out.println(e.getMessage()); 
524
      out.println("</error>");
525
    }
526
  }
527
  
528
  /** 
529
   * Handle the validtion request and return the results to the requestor
530
   */
531
  private void handleValidateAction(PrintWriter out, Hashtable params, 
532
               HttpServletResponse response) {
533

    
534
    // Get the document indicated
535
    String valtext = null;
536
    try {
537
      valtext = ((String[])params.get("valtext"))[0];
538
    } catch (Exception nullpe) {
539

    
540
      String docid = null;
541
      try {
542
        // Find the document id number
543
        docid = ((String[])params.get("docid"))[0]; 
544
  
545
        // Get the document indicated fromthe db
546
        valtext = docreader.readXMLDocument(docid);
547

    
548
      } catch (NullPointerException npe) {
549
        response.setContentType("text/html");
550
        out.println("Error getting document ID: " + docid );
551
      }
552
    }
553

    
554
    try {
555
      DBValidate valobj = new DBValidate(saxparser,xmlcatalogfile);
556
      boolean valid = valobj.validateString(valtext);
557

    
558
      // set content type and other response header fields first
559
      response.setContentType("text/html");
560
      out.println("<html>");
561
      out.println("<head><link rel=\"stylesheet\" type=\"text/css\" " +
562
                  "href=\"/xmltodb/rowcol.css\" /></head>");
563
      out.println("<body class=\"emlbody\">");
564
  
565
      if (valid) {
566
        out.println("The input XML is VALID!");
567
      } else {
568
        out.println("The input XML is NOT VALID<br />\n<pre>\n" 
569
                    + valobj.returnErrors() + "\n</pre>\n");
570
      } 
571
      out.println("</body></html>");
572
    } catch (NullPointerException npe2) {
573
      // set content type and other response header fields first
574
      response.setContentType("text/html");
575
      out.println("Error validating document."); 
576
    }
577
  }
578

    
579
  /** 
580
   * Handle the document request and return the results 
581
   * to the requestor
582
   */
583
  private void handleGetDataDocumentAction(PrintWriter out, Hashtable params, 
584
               HttpServletResponse response) {
585
      boolean error_flag = false;
586
      String error_message = "";
587
      // Get the document indicated
588
      String[] datadoc = (String[])params.get("datadoc");
589
      // defaultdatapath = "C:\\Temp\\";    // for testing only!!!
590
      // executescript = "test.bat";        // for testing only!!!
591
      
592
      // set content type and other response header fields first
593
      response.setContentType("application/octet-stream");
594
      if (defaultdatapath!=null) {
595
        if(!defaultdatapath.endsWith(System.getProperty("file.separator"))) {
596
          defaultdatapath=defaultdatapath+System.getProperty("file.separator");
597
        }
598
        System.out.println("Path= "+defaultdatapath+datadoc[0]);
599
        if (executescript!=null) {
600
          String command = null;
601
          File scriptfile = new File(executescript);
602
          if (scriptfile.exists()) {
603
            command=executescript+" "+datadoc[0]; // script includes path
604
        } else {     // look in defaultdatapath
605
            // on Win98 one MUST include the .bat extender
606
            command = defaultdatapath+executescript+" "+datadoc[0];  
607
        }
608
      System.out.println(command);
609
      try {
610
      Process proc = Runtime.getRuntime().exec(command);
611
      proc.waitFor();
612
      }
613
      catch (Exception eee) {
614
        System.out.println("Error running process!");
615
        error_flag = true;
616
        error_message = "Error running process!";}
617
      } // end executescript not null if
618
      File datafile = new File(defaultdatapath+datadoc[0]);
619
      try {
620
      FileInputStream fw = new FileInputStream(datafile);
621
      int x;
622
      while ((x = fw.read())!=-1) {
623
        out.write(x); }
624
        fw.close();
625
      } catch (Exception e) {
626
        System.out.println("Error in returning file\n"+e.getMessage());
627
        error_flag=true;
628
        error_message = error_message+"\nError in returning file\n"+
629
                        e.getMessage();
630
      }
631
    } // end defaultdatapath not null if
632
  }
633
}
634

    
635
/**
636
 * '$Log$
637
 * 'Revision 1.33  2000/06/27 04:50:33  jones
638
 * 'Updated javadoc documentation.
639
 * '
640
 * 'Revision 1.32  2000/06/27 04:31:07  jones
641
 * 'Fixed bugs associated with the new UPDATE and DELETE functions of
642
 * 'DBWriter.  There were problematic interactions between some static
643
 * 'variables used in DBEntityResolver and the way in which the
644
 * 'Servlet objects are re-used across multiple client invocations.
645
 * '
646
 * 'Generally cleaned up error reporting.  Now all errors and success
647
 * 'results are reported as XML documents from MetaCatServlet.  Need
648
 * 'to make the command line tools do the same.
649
 * '
650
 * 'Revision 1.31  2000/06/26 10:35:05  jones
651
 * 'Merged in substantial changes to DBWriter and associated classes and to
652
 * 'the MetaCatServlet in order to accomodate the new UPDATE and DELETE
653
 * 'functions.  The command line tools and the parameters for the
654
 * 'servlet have changed substantially.
655
 * '
656
 * 'Revision 1.30.2.6  2000/06/26 10:18:06  jones
657
 * 'Partial fix for MetaCatServlet INSERT?UPDATE bug.  Only will work on
658
 * 'the first call to the servlet.  Subsequent calls fail.  Seems to be
659
 * 'related to exception handling.  Multiple successive DELETE actions
660
 * 'work fine.
661
 * '
662
 * 'Revision 1.30.2.5  2000/06/26 09:09:53  jones
663
 * 'Modified MetaCatServlet and associated files to handle the UPDATE
664
 * 'and DELETE actions for DBWriter.
665
 * '
666
 * 'Revision 1.30.2.4  2000/06/26 00:51:06  jones
667
 * 'If docid passed to DBWriter.write() is not unique, classes now generate
668
 * 'an AccessionNumberException containing the new docid generated as a
669
 * 'replacement.  The docid is then extracted from the exception and
670
 * 'returned to the calling application for user feedback or client processing.
671
 * '
672
 * 'Revision 1.30.2.3  2000/06/25 23:38:17  jones
673
 * 'Added RCSfile keyword
674
 * '
675
 * 'Revision 1.30.2.2  2000/06/25 23:34:18  jones
676
 * 'Changed documentation formatting, added log entries at bottom of source files
677
 * ''
678
 */
(17-17/21)