Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that searches a relational DB for elements and 
4
 *             attributes that have free text matches a query string,
5
 *             or structured query matches to a path specified node in the 
6
 *             XML hierarchy.  It returns a result set consisting of the 
7
 *             document ID for each document that satisfies the query
8
 *  Copyright: 2000 Regents of the University of California and the
9
 *             National Center for Ecological Analysis and Synthesis
10
 *    Authors: Matt Jones
11
 *    Release: @release@
12
 *
13
 *   '$Author: berkley $'
14
 *     '$Date: 2000-09-15 12:52:12 -0700 (Fri, 15 Sep 2000) $'
15
 * '$Revision: 453 $'
16
 */
17

    
18
package edu.ucsb.nceas.metacat;
19

    
20
import java.io.*;
21
import java.util.Vector;
22
import java.net.URL;
23
import java.net.MalformedURLException;
24
import java.sql.*;
25
import java.util.Stack;
26
import java.util.Hashtable;
27
import java.util.Enumeration;
28

    
29
/** 
30
 * A Class that searches a relational DB for elements and 
31
 * attributes that have free text matches a query string,
32
 * or structured query matches to a path specified node in the 
33
 * XML hierarchy.  It returns a result set consisting of the 
34
 * document ID for each document that satisfies the query
35
 */
36
public class DBQuery {
37

    
38
  static final int ALL = 1;
39
  static final int WRITE = 2;
40
  static final int READ = 4;
41

    
42
  private Connection	conn = null;
43
  private String	parserName = null;
44

    
45
  /**
46
   * the main routine used to test the DBQuery utility.
47
   * <p>
48
   * Usage: java DBQuery <xmlfile>
49
   *
50
   * @param xmlfile the filename of the xml file containing the query
51
   */
52
  static public void main(String[] args) {
53
     
54
     if (args.length < 1)
55
     {
56
        System.err.println("Wrong number of arguments!!!");
57
        System.err.println("USAGE: java DBQuery <xmlfile>");
58
        return;
59
     } else {
60
        try {
61
                    
62
          String xmlfile  = args[0];
63

    
64
          // Open a connection to the database
65
          MetaCatUtil   util = new MetaCatUtil();
66
          Connection dbconn = util.openDBConnection();
67

    
68
          // Execute the query
69
          DBQuery queryobj = new DBQuery(dbconn, util.getOption("saxparser"));
70
          FileReader xml = new FileReader(new File(xmlfile));
71
          Hashtable nodelist = null;
72
          nodelist = queryobj.findDocuments(xml, null, null);
73

    
74
          // Print the reulting document listing
75
          StringBuffer result = new StringBuffer();
76
          String document = null;
77
          String docid = null;
78
          result.append("<?xml version=\"1.0\"?>\n");
79
          result.append("<resultset>\n"); 
80
  // following line removed by Dan Higgins to avoid insertion of query XML inside returned XML doc
81
  //        result.append("  <query>" + xmlfile + "</query>\n");
82
          Enumeration doclist = nodelist.keys(); 
83
          while (doclist.hasMoreElements()) {
84
            docid = (String)doclist.nextElement();
85
            document = (String)nodelist.get(docid);
86
            result.append("  <document>\n    " + document + 
87
                          "\n  </document>\n");
88
          }
89
          result.append("</resultset>\n");
90

    
91
          System.out.println(result);
92

    
93
        } catch (Exception e) {
94
          System.err.println("EXCEPTION HANDLING REQUIRED");
95
          System.err.println(e.getMessage());
96
          e.printStackTrace(System.err);
97
        }
98
     }
99
  }
100
  
101
  /**
102
   * construct an instance of the DBQuery class 
103
   *
104
   * <p>Generally, one would call the findDocuments() routine after creating 
105
   * an instance to specify the search query</p>
106
   *
107
   * @param conn the JDBC connection that we use for the query
108
   * @param parserName the fully qualified name of a Java class implementing
109
   *                   the org.xml.sax.XMLReader interface
110
   */
111
  public DBQuery( Connection conn, String parserName ) 
112
                  throws IOException, 
113
                         SQLException, 
114
                         ClassNotFoundException {
115
    this.conn = conn;
116
    this.parserName = parserName;
117
  }
118
  
119
  /** 
120
   * routine to search the elements and attributes looking to match query
121
   *
122
   * @param xmlquery the xml serialization of the query (@see pathquery.dtd)
123
   */
124
  public Hashtable findDocuments(Reader xmlquery, String user, String group)
125
         //throws Exception
126
  {
127
      Hashtable	 docListResult = new Hashtable();
128
      PreparedStatement pstmt;
129
      String docid = null;
130
      String docname = null;
131
      String doctype = null;
132
      String doctitle = null;
133
      String createDate = null;
134
      String updateDate = null;
135
      String fieldname = null;
136
      String fielddata = null;
137
      String relation = null;
138
      StringBuffer document = null; 
139

    
140
      try {
141
        // Get the XML query and covert it into a SQL statment
142
        QuerySpecification qspec = new QuerySpecification(xmlquery, 
143
                                   parserName);
144
        //System.out.println(qspec.printSQL());
145
        pstmt = conn.prepareStatement( qspec.printSQL() );
146

    
147
        // Execute the SQL query using the JDBC connection
148
        pstmt.execute();
149
        ResultSet rs = pstmt.getResultSet();
150
        boolean tableHasRows = rs.next();
151
        while (tableHasRows) {
152
          docid = rs.getString(1);
153
          if ( !hasReadPermission(conn, docid, user, group) ) {continue;}
154
          docname = rs.getString(2);
155
          doctype = rs.getString(3);
156
          doctitle = rs.getString(4);
157
          createDate = rs.getString(5);
158
          updateDate = rs.getString(6);
159
          
160
          document = new StringBuffer();
161
          
162
          document.append("<docid>").append(docid).append("</docid>");
163
          if (docname != null) {
164
            document.append("<docname>" + docname + "</docname>");
165
          }
166
          if (doctype != null) {
167
            document.append("<doctype>" + doctype + "</doctype>");
168
          }
169
          if (doctitle != null) {
170
            document.append("<doctitle>" + doctitle + "</doctitle>");
171
          }
172
          if(createDate != null) {
173
            document.append("<createdate>" + createDate + "</createdate>");
174
          }
175
          if(updateDate != null) {
176
            document.append("<updatedate>" + updateDate + "</updatedate>");
177
          }
178

    
179
          // Store the document id and the root node id
180
          docListResult.put(docid,(String)document.toString());
181

    
182
          // Advance to the next record in the cursor
183
          tableHasRows = rs.next();
184
        }
185
        
186
        if(qspec.containsExtendedSQL())
187
        {
188
          Vector extendedFields = new Vector(qspec.getReturnFieldList());
189
          Vector results = new Vector();
190
          pstmt = conn.prepareStatement(qspec.printExtendedSQL());
191
          pstmt.execute();
192
          rs = pstmt.getResultSet();
193
          tableHasRows = rs.next();
194
          while(tableHasRows) 
195
          {
196
            docid = rs.getString(1);
197
            if ( !hasReadPermission(conn, docid, user, group) ) {continue;}
198
            fieldname = rs.getString(2);
199
            fielddata = rs.getString(3);
200
            
201
            document = new StringBuffer();
202

    
203
            document.append("<param name=\"");
204
            document.append(fieldname);
205
            document.append("\">");
206
            document.append(fielddata);
207
            document.append("</param>");
208

    
209
            tableHasRows = rs.next();
210
            if(docListResult.containsKey(docid))
211
            {
212
              String removedelement = (String)docListResult.remove(docid);
213
              docListResult.put(docid, removedelement + document.toString());
214
            }
215
            else
216
            {
217
              docListResult.put(docid, document.toString()); 
218
            }
219
          }
220
        }
221

    
222
        pstmt = conn.prepareStatement(qspec.printPackageSQL());
223
        pstmt.execute();
224
        rs = pstmt.getResultSet();
225
        tableHasRows = rs.next();
226
        String[][] relations = new String[2000][3];
227
        int relLength=0;
228
        while(tableHasRows)
229
        {
230
          relations[relLength][0] = rs.getString(1).trim();
231
          relations[relLength][1] = rs.getString(2).trim();
232
          relations[relLength][2] = rs.getString(3).trim();
233
          relLength++;
234
          
235
          //this get's direct relations from the data.  i.e. those relations
236
          //where the docid of the current document is in the subject tag.
237
          
238
          docid = rs.getString(1);
239
          metacatURL murl = new metacatURL(docid);
240
          if(murl.getURLType().equals("metacat"))
241
          {//we only want to process metacat urls here.
242
            String[] tempparam = murl.getParam(0);
243
            if(tempparam[0].equals("docid"))
244
            {
245
              docid = tempparam[1]; 
246
              document = new StringBuffer();
247
              document.append("<relation>");
248
              document.append("<relationtype>").append(rs.getString(2));
249
              document.append("</relationtype>");
250
              document.append("<relationdoc>").append(rs.getString(3));
251
              document.append("</relationdoc>");
252
              document.append("</relation>");
253
              tableHasRows = rs.next();
254
              if(docListResult.containsKey(docid))
255
              {
256
                String removedelement = (String)docListResult.remove(docid);
257
                docListResult.put(docid, removedelement + document.toString());
258
              }
259
              else
260
              {
261
                docListResult.put(docid, document.toString()); 
262
              }
263
            }
264
            else
265
            {
266
              //throw new Exception("Error in url. The first parameter must " +
267
              //                    "be the docid.");
268
              System.err.println("DBQuery: error in url");
269
            }
270
          }
271
        }
272
        
273
        //this loop handles transitive relations. i.e. if two relation tags
274
        //both have the same object then the subject of each are related.
275
        for(int i=0; i<relLength; i++)
276
        {
277
          for(int j=0; j<relLength; j++)
278
          {
279
            if(i != j)
280
            {
281
              if(relations[i][2].equals(relations[j][2]))
282
              {//the objects are the same.
283
                metacatURL murl = new metacatURL(relations[i][0]);
284
                //relations[i][0] contains the docid of document that 
285
                //the new document is related to.
286
                String[] tempparam = murl.getParam(0);
287
                if(tempparam[0].equals("docid"))
288
                {
289
                  docid = tempparam[1]; 
290
                  document = new StringBuffer();
291
                  document.append("<relation>");
292
                  document.append("<relationtype>").append(relations[j][1]);
293
                  document.append("</relationtype>");
294
                  document.append("<relationdoc>").append(relations[j][0]);
295
                  //the relation is to the subject of the new document
296
                  //instead of the object.
297
                  //   direct relation    transitive relation
298
                  // |-----------------|  |-----------------|
299
                  // subject -> (object = object) -> subject
300
                  document.append("</relationdoc>");
301
                  document.append("</relation>");
302
                  if(docListResult.containsKey(docid))
303
                  {
304
                    String removedelement = (String)docListResult.remove(docid);
305
                    docListResult.put(docid, removedelement + 
306
                                      document.toString());
307
                  }
308
                  else
309
                  {
310
                    docListResult.put(docid, document.toString()); 
311
                  }
312
                }
313
              }
314
            }
315
          }
316
        }
317

    
318
        pstmt.close();
319
      } catch (SQLException e) {
320
        System.err.println("Error getting id: " + e.getMessage());
321
      } catch (IOException ioe) {
322
        System.err.println("Error printing qspec:");
323
        System.err.println(ioe.getMessage());
324
      }
325
    //System.out.println("docListResult: ");
326
    //System.out.println(docListResult.toString());
327
    return docListResult;
328
  }
329
  
330
  /**
331
   * returns a string array of the contents of a particular node. 
332
   * If the node appears more than once, the contents are returned 
333
   * in the order in which they appearred in the document.
334
   * @param nodename the name or path of the particular node.
335
   * @param docid the docid of the document you want the node from.
336
   * @param conn a database connection-this allows this method to be static
337
   */
338
  public static Object[] getNodeContent(String nodename, String docid, 
339
                                        Connection conn)
340
  {
341
    StringBuffer query = new StringBuffer();
342
    Vector result = new Vector();
343
    PreparedStatement pstmt;
344
    query.append("select nodedata from xml_nodes where parentnodeid in ");
345
    query.append("(select nodeid from xml_index where path like '");
346
    query.append(nodename);
347
    query.append("' and docid like '").append(docid).append("')");
348
    try
349
    {
350
      pstmt = conn.prepareStatement(query.toString());
351

    
352
      // Execute the SQL query using the JDBC connection
353
      pstmt.execute();
354
      ResultSet rs = pstmt.getResultSet();
355
      boolean tableHasRows = rs.next();
356
      while (tableHasRows) 
357
      {
358
        result.add(rs.getString(1));
359
        System.out.println(rs.getString(1));
360
        tableHasRows = rs.next();
361
      }
362
    } 
363
    catch (SQLException e) 
364
    {
365
      System.err.println("Error getting id: " + e.getMessage());
366
    } 
367
    
368
    return result.toArray();
369
  }
370
  
371
  /**
372
   * format a structured query as an XML document that conforms
373
   * to the pathquery.dtd and is appropriate for submission to the DBQuery
374
   * structured query engine
375
   *
376
   * @param params The list of parameters that  should be included in the query
377
   */
378
  public static String createSQuery(Hashtable params)
379
  { 
380
    StringBuffer query = new StringBuffer();
381
    Enumeration elements;
382
    Enumeration keys;
383
    String doctype = null;
384
    String casesensitive = null;
385
    String searchmode = null;
386
    Object nextkey;
387
    Object nextelement;
388
    //add the xml headers
389
    query.append("<?xml version=\"1.0\"?>\n");
390
    query.append("<pathquery version=\"1.0\"><meta_file_id>");
391
    
392
    if(params.containsKey("meta_file_id"))
393
    {
394
      query.append( ((String[])params.get("meta_file_id"))[0]);
395
    	query.append("</meta_file_id>");
396
    }
397
    else
398
    {
399
      query.append("unspecified</meta_file_id>");
400
    }
401
    
402
    query.append("<querytitle>");
403
    if(params.containsKey("querytitle"))
404
    {
405
      query.append(((String[])params.get("querytitle"))[0]);
406
    	query.append("</querytitle>");
407
    }
408
    else
409
    {
410
    	query.append("unspecified</querytitle>");
411
    }
412
    
413
    if(params.containsKey("doctype"))
414
    {
415
      doctype = ((String[])params.get("doctype"))[0]; 
416
    }
417
    else
418
    {
419
      doctype = "ANY";  
420
    }
421
    
422
    if(params.containsKey("returnfield"))
423
    {
424
      String[] returnfield = ((String[])params.get("returnfield"));
425
      for(int i=0; i<returnfield.length; i++)
426
      {
427
        query.append("<returnfield>").append(returnfield[i]);
428
        query.append("</returnfield>");
429
      }
430
    }
431
    
432
    //if you don't limit the query by doctype, then it just creates
433
    //an empty returndoctype tag.
434
    if (!doctype.equals("any") && 
435
        !doctype.equals("ANY") &&
436
        !doctype.equals("") ) 
437
    {
438
       query.append("<returndoctype>");
439
       query.append(doctype).append("</returndoctype>");
440
    }
441
    else
442
    { 
443
      query.append("<returndoctype></returndoctype>");
444
    }
445
    
446
    //allows the dynamic switching of boolean operators
447
    if(params.containsKey("operator"))
448
    {
449
      query.append("<querygroup operator=\"" + 
450
    		        ((String[])params.get("operator"))[0] + "\">");
451
    }
452
    else
453
    { //the default operator is UNION
454
      query.append("<querygroup operator=\"UNION\">"); 
455
    }
456
    		
457
    if(params.containsKey("casesensitive"))
458
    {
459
      casesensitive = ((String[])params.get("casesensitive"))[0]; 
460
    }
461
    else
462
    {
463
      casesensitive = "false"; 
464
    }
465
    
466
    if(params.containsKey("searchmode"))
467
    {
468
      searchmode = ((String[])params.get("searchmode"))[0]; 
469
    }
470
    else
471
    {
472
      searchmode = "contains"; 
473
    }
474
    		
475
    //anyfield is a special case because it does a 
476
    //free text search.  It does not have a <pathexpr>
477
    //tag.  This allows for a free text search within the structured
478
    //query.  This is useful if the INTERSECT operator is used.
479
    if(params.containsKey("anyfield"))
480
    {
481
       String[] anyfield = ((String[])params.get("anyfield"));
482
       //allow for more than one value for anyfield
483
       for(int i=0; i<anyfield.length; i++)
484
       {
485
         if(!anyfield[i].equals(""))
486
         {
487
           query.append("<queryterm casesensitive=\"" + casesensitive + 
488
                        "\" " + "searchmode=\"" + searchmode + "\"><value>" +
489
    			              anyfield[i] +
490
    			              "</value></queryterm>"); 
491
         }
492
       }
493
    }
494
    		
495
    //this while loop finds the rest of the parameters
496
    //and attempts to query for the field specified
497
    //by the parameter.
498
    elements = params.elements();
499
    keys = params.keys();
500
    while(keys.hasMoreElements() && elements.hasMoreElements())
501
    {
502
      nextkey = keys.nextElement();
503
    	nextelement = elements.nextElement();
504

    
505
    	//make sure we aren't querying for any of these
506
    	//parameters since the are already in the query
507
      //in one form or another.
508
    	if(!nextkey.toString().equals("doctype") && 
509
    		 !nextkey.toString().equals("action")  &&
510
    		 !nextkey.toString().equals("qformat") && 
511
    		 !nextkey.toString().equals("anyfield") &&
512
         !nextkey.toString().equals("returnfield") &&
513
    		 !nextkey.toString().equals("operator") )
514
    	{
515
        //allow for more than value per field name
516
        for(int i=0; i<((String[])nextelement).length; i++)
517
        {
518
          if(!((String[])nextelement)[i].equals(""))
519
          {
520
            query.append("<queryterm casesensitive=\"" + casesensitive +"\" " + 
521
    				             "searchmode=\"" + searchmode + "\">" +
522
    		                 "<value>" +
523
                         //add the query value
524
    		                 ((String[])nextelement)[i] +
525
    		                 "</value><pathexpr>" +
526
    		                 //add the path to query by 
527
                         nextkey.toString() + 
528
                         "</pathexpr></queryterm>");
529
          }
530
        }
531
    	}
532
    }
533
    query.append("</querygroup></pathquery>");
534
    //append on the end of the xml and return the result as a string
535
    return query.toString();
536
  }
537
  
538
  /**
539
   * format a simple free-text value query as an XML document that conforms
540
   * to the pathquery.dtd and is appropriate for submission to the DBQuery
541
   * structured query engine
542
   *
543
   * @param value the text string to search for in the xml catalog
544
   * @param doctype the type of documents to include in the result set -- use
545
   *        "any" or "ANY" for unfiltered result sets
546
   */
547
   public static String createQuery(String value, String doctype) {
548
     StringBuffer xmlquery = new StringBuffer();
549
     xmlquery.append("<?xml version=\"1.0\"?>\n");
550
     xmlquery.append("<pathquery version=\"1.0\">");
551
     xmlquery.append("<meta_file_id>Unspecified</meta_file_id>");
552
     xmlquery.append("<querytitle>Unspecified</querytitle>");
553

    
554
     if (!doctype.equals("any") && !doctype.equals("ANY")) {
555
       xmlquery.append("<returndoctype>");
556
       xmlquery.append(doctype).append("</returndoctype>");
557
     }
558

    
559
     xmlquery.append("<querygroup operator=\"UNION\">");
560
     //chad added - 8/14
561
     //the if statement allows a query to gracefully handle a null 
562
     //query.  Without this if a nullpointerException is thrown.
563
     if(!value.equals(""))
564
     {
565
       xmlquery.append("<queryterm casesensitive=\"false\" ");
566
       xmlquery.append("searchmode=\"contains\">");
567
       xmlquery.append("<value>").append(value).append("</value>");
568
       xmlquery.append("</queryterm>");
569
     }
570
     xmlquery.append("</querygroup>");
571
     xmlquery.append("</pathquery>");
572

    
573
     
574
     return (xmlquery.toString());
575
   }
576

    
577
  /**
578
   * format a simple free-text value query as an XML document that conforms
579
   * to the pathquery.dtd and is appropriate for submission to the DBQuery
580
   * structured query engine
581
   *
582
   * @param value the text string to search for in the xml catalog
583
   */
584
   public static String createQuery(String value) {
585
     return createQuery(value, "any");
586
   }
587
   
588
  /** Check for "read" permissions from DB connection */
589
  private boolean hasReadPermission(Connection conn, String docid, 
590
                                     String user, String group) 
591
                                     throws SQLException {
592
    // b' of the command line invocation
593
    if ( (user == null) && (group == null) ) {
594
      return true;
595
    }
596
    
597
    PreparedStatement pstmt;
598
    // checking if user is owner of docid or if docid has public access
599
    try {
600
      pstmt = conn.prepareStatement(
601
                   "SELECT 'x' FROM xml_documents " +
602
                   "WHERE docid LIKE ? AND user_owner LIKE ? " + 
603
                   "UNION " +
604
                   "SELECT 'x' FROM xml_documents " +
605
                   "WHERE docid LIKE ? AND public_access = 1");
606
      // Bind the values to the query
607
      pstmt.setString(1, docid);
608
      pstmt.setString(2, user);
609
      pstmt.setString(3, docid);
610

    
611
      pstmt.execute();
612
      ResultSet rs = pstmt.getResultSet();
613
      boolean hasRow = rs.next();
614
      pstmt.close();
615
      if (hasRow) {
616
        return true;
617
      }
618
      
619
    } catch (SQLException e) {
620
      throw new 
621
        SQLException("Error checking document's owner or public access: "
622
                      + e.getMessage());
623
    }
624

    
625
    // checking if docid has public access at this time
626
    try {
627
      pstmt = conn.prepareStatement(
628
                   "SELECT 'x' FROM xml_access " +
629
                   "WHERE docid LIKE ? " +
630
                   "AND principal_name = 'public' " +
631
                   "AND principal_type = 'user' " +
632
                   "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
633
                                   "AND nvl(end_time,sysdate)");
634
      // Bind the values to the query
635
      pstmt.setString(1, docid);
636

    
637
      pstmt.execute();
638
      ResultSet rs = pstmt.getResultSet();
639
      boolean hasRow = rs.next();
640
      pstmt.close();
641
      if (hasRow) {
642
        return true;
643
      }
644
      
645
    } catch (SQLException e) {
646
      throw new 
647
        SQLException("Error checking doc's public access: " + e.getMessage());
648
    }
649

    
650
    // checking access type from xml_access table
651
    int accesstype = 0;
652
    try {
653
      pstmt = conn.prepareStatement(
654
                   "SELECT access_type FROM xml_access " +
655
                   "WHERE docid LIKE ? " + 
656
                   "AND principal_name LIKE ? " +
657
                   "AND principal_type = 'user' " +
658
                   "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
659
                                   "AND nvl(end_time,sysdate) " +
660
                   "UNION " +
661
                   "SELECT access_type FROM xml_access " +
662
                   "WHERE docid LIKE ? " + 
663
                   "AND principal_name LIKE ? " +
664
                   "AND principal_type = 'group' " +
665
                   "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
666
                                   "AND nvl(end_time,sysdate)");
667
      // Bind the values to the query
668
      pstmt.setString(1, docid);
669
      pstmt.setString(2, user);
670
      pstmt.setString(3, docid);
671
      pstmt.setString(2, group);
672

    
673
      pstmt.execute();
674
      ResultSet rs = pstmt.getResultSet();
675
      boolean hasRows = rs.next();
676
      while ( hasRows ) {
677
        accesstype = rs.getInt(1);
678
        if ( (accesstype & READ) == READ ) {
679
          pstmt.close();
680
          return true;
681
        }
682
        hasRows = rs.next();
683
      }
684

    
685
      pstmt.close();
686
      return false;
687
      
688
    } catch (SQLException e) {
689
      throw new 
690
      SQLException("Error getting document's permissions: " + e.getMessage());
691
    }
692
  }
693
   
694
}
695

    
696
/**
697
 * '$Log$
698
 * 'Revision 1.19  2000/09/12 17:37:07  bojilova
699
 * 'added check from "read" permission on "query" and "squery" actions
700
 * 'for connected user or for "public" connection
701
 * '
702
 * 'Revision 1.18  2000/09/05 20:50:56  berkley
703
 * 'Added a method called getNodeContent which retrieves the content of a node in a document.  If there are more than one nodes with the same name returned, it returns an array with all of the data.
704
 * '
705
 * 'Revision 1.17  2000/08/31 21:20:39  berkley
706
 * 'changed xslf for new returnfield scheme.  the returnfields are now returned as <param name="<returnfield>"> tags.
707
 * 'hThe sql for the returnfield query was redone to fix a previous problem with slow queries
708
 * '
709
 * 'Revision 1.16  2000/08/23 22:55:25  berkley
710
 * 'changed the field names to be case-sensitive in the returnfields
711
 * '
712
 * 'Revision 1.15  2000/08/23 17:22:07  berkley
713
 * 'added support for the returnfield parameter
714
 * '-added the dynamic parameters to the returned hash table of documents
715
 * '
716
 * 'Revision 1.14  2000/08/17 16:02:34  berkley
717
 * 'Made changes to createSQuery to allow for multiple parameters of the same name.  Also changed the param list to include only "Hashtable params" without a "String doctype" since the doctype is already contained in the params.
718
 * '
719
 * 'Revision 1.13  2000/08/14 21:26:12  berkley
720
 * 'Added createSQuery() to handle structured queries of an arbitrary number of parameters.  Also modified createQuery() to handle a null query in a graceful manner.
721
 * '
722
 * 'Revision 1.12  2000/08/14 20:53:33  jones
723
 * 'Added "release" keyword to all metacat source files so that the release
724
 * 'number will be evident in software distributions.
725
 * '
726
 * 'Revision 1.11  2000/08/11 18:26:07  berkley
727
 * 'added createSQuery
728
 * '
729
 * 'Revision 1.10  2000/07/26 20:40:41  higgins
730
 * 'no message
731
 * '
732
 * 'Revision 1.9  2000/06/26 10:35:04  jones
733
 * 'Merged in substantial changes to DBWriter and associated classes and to
734
 * 'the MetaCatServlet in order to accomodate the new UPDATE and DELETE
735
 * 'functions.  The command line tools and the parameters for the
736
 * 'servlet have changed substantially.
737
 * '
738
 * 'Revision 1.8.2.2  2000/06/25 23:38:16  jones
739
 * 'Added RCSfile keyword
740
 * '
741
 * 'Revision 1.8.2.1  2000/06/25 23:34:17  jones
742
 * 'Changed documentation formatting, added log entries at bottom of source files
743
 * ''
744
 */
(8-8/27)