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-12-20 10:20:18 -0800 (Wed, 20 Dec 2000) $'
15
 * '$Revision: 626 $'
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
  private MetaCatUtil util = new MetaCatUtil();
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
  public Hashtable findDocuments(Reader xmlquery, String user, String group)
120
  {
121
    return findDocuments(xmlquery, user, group, null);
122
  }
123
  
124
  /** 
125
   * routine to search the elements and attributes looking to match query
126
   *
127
   * @param xmlquery the xml serialization of the query (@see pathquery.dtd)
128
   * @param user the username of the user
129
   * @param group the group of the user
130
   * @param returndoc an array of document types to backtrack against.
131
   */
132
  public Hashtable findDocuments(Reader xmlquery, String user, String group,
133
                                 String[] returndoc)
134
  {
135
    //System.out.println("in finddocuments");
136
      Hashtable   docListResult = new Hashtable();
137
      PreparedStatement pstmt;
138
      String docid = null;
139
      String docname = null;
140
      String doctype = null;
141
      String doctitle = null;
142
      String createDate = null;
143
      String updateDate = null;
144
      String fieldname = null;
145
      String fielddata = null;
146
      String relation = null;
147
      int rev = 0;
148
      StringBuffer document = null; 
149
      Vector returndocVec = new Vector();
150
      
151
      if(returndoc != null)
152
      {//add the returndoc elements to a vector for easier manipulation
153
        for(int i=0; i<returndoc.length; i++)
154
        {
155
          returndocVec.add(new String((String)returndoc[i]));
156
        }
157
      }
158
      
159
      try {
160
        // Get the XML query and covert it into a SQL statment
161
        QuerySpecification qspec = new QuerySpecification(xmlquery, 
162
                                   parserName, 
163
                                   util.getOption("accNumSeparator"));
164
       // System.out.println(qspec.printSQL());
165
        pstmt = conn.prepareStatement( qspec.printSQL() );
166

    
167
        // Execute the SQL query using the JDBC connection
168
        pstmt.execute();
169
        ResultSet rs = pstmt.getResultSet();
170
        boolean tableHasRows = rs.next();
171
        while (tableHasRows) {
172
          docid = rs.getString(1);
173
          if ( !hasPermission(conn, user, group, docid) ) {
174
            // Advance to the next record in the cursor
175
            tableHasRows = rs.next();
176
            continue;
177
          }
178
          docname = rs.getString(2);
179
          doctype = rs.getString(3);
180
          doctitle = rs.getString(4);
181
          createDate = rs.getString(5);
182
          updateDate = rs.getString(6);
183
          rev = rs.getInt(7);
184
          //System.out.println("vec.size = " + returndocVec.size());
185
          if(returndocVec.size() != 0 && !returndocVec.contains(doctype))
186
          { //there are returndocs to match (backtracking can now be performed). 
187
            //System.out.println("olddoctype: " + doctype);
188
            StringBuffer btBuf = new StringBuffer();
189
            btBuf.append("select object from xml_relation where ");
190
            btBuf.append("objdoctype in (");
191
            //build the doctype list for the backtracking sql statement
192
            for(int i=0; i<returndocVec.size(); i++)
193
            {
194
              btBuf.append("'").append((String)returndocVec.get(i)).append("'");
195
              if(i != (returndocVec.size() - 1))
196
              {
197
                btBuf.append(", ");
198
              } 
199
            }
200
            btBuf.append(") ");
201
            btBuf.append("and subject like '");
202
            //btBuf.append("metacat://").append(util.getOption("server"));
203
            //btBuf.append("?docid=").append(docid).append("'");
204
            btBuf.append("%docid=").append(docid).append("'");
205
            //System.out.println("sql: " + btBuf.toString());
206
            pstmt = conn.prepareStatement(btBuf.toString());
207
            pstmt.execute();
208
            ResultSet btrs = pstmt.getResultSet();
209
            boolean hasBtRows = btrs.next();
210
            if(hasBtRows)
211
            { //there was a backtrackable document found
212
              DocumentImpl xmldoc = null;
213
              //System.out.println("document found is: " + btrs.getString(1));
214
              MetacatURL objURL = new MetacatURL(btrs.getString(1));
215
              try
216
              {
217
                xmldoc = new DocumentImpl(conn, objURL.getParam(0)[1]);
218
              }
219
              catch(Exception e)
220
              {
221
                System.out.println("Error getting document: " + e.getMessage());
222
              }
223
              
224
              docid   = xmldoc.getDocID();
225
              docname = xmldoc.getDocname();
226
              doctype = xmldoc.getDoctype();
227
              doctitle = xmldoc.getDocTitle();
228
              createDate = xmldoc.getCreateDate();
229
              updateDate = xmldoc.getUpdateDate();
230
              //System.out.println("docname: " + docname + " doctype: " + doctype + 
231
              //                   " doctitle: " + doctitle + " createdate: " +
232
              //                   createDate + " updatedate: " + updateDate);
233
            }
234
            btrs.close();
235
          }
236
          
237
          document = new StringBuffer();
238
          //System.out.println("packagdoctype: " + util.getOption("packagedoctype"));
239
          //if(!doctype.equals(util.getOption("packagedoctype")))
240
          {
241
            String completeDocid = docid + util.getOption("accNumSeparator");
242
            completeDocid += rev;
243
            document.append("<docid>").append(completeDocid).append("</docid>");
244
            if (docname != null) {
245
              document.append("<docname>" + docname + "</docname>");
246
            }
247
            if (doctype != null) {
248
              document.append("<doctype>" + doctype + "</doctype>");
249
            }
250
            if (doctitle != null) {
251
              document.append("<doctitle>" + doctitle + "</doctitle>");
252
            }
253
            if(createDate != null) {
254
              document.append("<createdate>" + createDate + "</createdate>");
255
            }
256
            if(updateDate != null) {
257
              document.append("<updatedate>" + updateDate + "</updatedate>");
258
            }
259
            // Store the document id and the root node id
260
            docListResult.put(docid,(String)document.toString());
261
          }
262

    
263
          // Advance to the next record in the cursor
264
          tableHasRows = rs.next();
265
        }
266
        
267
        if(qspec.containsExtendedSQL())
268
        {
269
          Vector extendedFields = new Vector(qspec.getReturnFieldList());
270
          Vector results = new Vector();
271
          Enumeration keylist = docListResult.keys();
272
          StringBuffer doclist = new StringBuffer();
273
          while(keylist.hasMoreElements())
274
          {
275
            doclist.append("'");
276
            doclist.append((String)keylist.nextElement());
277
            doclist.append("',");
278
          }
279
          doclist.deleteCharAt(doclist.length()-1); //remove the last comma
280
          pstmt = conn.prepareStatement(qspec.printExtendedSQL(
281
                                        doclist.toString()));
282
          pstmt.execute();
283
          rs = pstmt.getResultSet();
284
          tableHasRows = rs.next();
285
          while(tableHasRows) 
286
          {
287
            docid = rs.getString(1);
288
            if ( !hasPermission(conn, user, group, docid) ) {continue;}
289
            fieldname = rs.getString(2);
290
            fielddata = rs.getString(3);
291
            
292
            document = new StringBuffer();
293

    
294
            document.append("<param name=\"");
295
            document.append(fieldname);
296
            document.append("\">");
297
            document.append(fielddata);
298
            document.append("</param>");
299

    
300
            tableHasRows = rs.next();
301
            if(docListResult.containsKey(docid))
302
            {
303
              String removedelement = (String)docListResult.remove(docid);
304
              docListResult.put(docid, removedelement + document.toString());
305
            }
306
            else
307
            {
308
              docListResult.put(docid, document.toString()); 
309
            }
310
          }
311
        }
312

    
313
        //this loop adds the relation data to the resultdoc
314
        //this code might be able to be added to the backtracking code above
315
        Enumeration docidkeys = docListResult.keys();
316
        while(docidkeys.hasMoreElements())
317
        {
318
          //String connstring = "metacat://"+util.getOption("server")+"?docid=";
319
          String connstring = "%docid=";
320
          String docidkey = (String)docidkeys.nextElement();
321
          //System.out.println("relationsql: " + qspec.printRelationSQL(
322
          //                                           connstring + docidkey));
323
          pstmt = conn.prepareStatement(
324
                  qspec.printRelationSQL(connstring + docidkey));
325
          pstmt.execute();
326
          rs = pstmt.getResultSet();
327
          tableHasRows = rs.next();
328
          while(tableHasRows)
329
          {
330
            String sub = rs.getString(1);
331
            String rel = rs.getString(2);
332
            String obj = rs.getString(3);
333
            String subDT = rs.getString(4);
334
            String objDT = rs.getString(5);
335
            
336
            MetacatURL murl = new MetacatURL(sub);
337
            if(murl.getProtocol().equals("metacat"))
338
            {//we only want to process metacat urls here.
339
              String[] tempparam = murl.getParam(0);
340
              if(tempparam[0].equals("docid") && tempparam[1].equals(docidkey))
341
              {
342
                document = new StringBuffer();
343
                document.append("<relation>");
344
                document.append("<relationtype>").append(rel);
345
                document.append("</relationtype>");
346
                document.append("<relationdoc>").append(obj);
347
                document.append("</relationdoc>");
348
                document.append("<relationdoctype>").append(objDT);
349
                document.append("</relationdoctype>");
350
                document.append("</relation>");
351
                
352
                String removedelement = (String)docListResult.remove(docidkey);
353
                docListResult.put(docidkey, removedelement + document.toString());
354
                
355
              }
356
            }
357
            tableHasRows = rs.next();
358
          }
359
        }
360
        pstmt.close();
361
      } catch (SQLException e) {
362
        System.err.println("Error getting id: " + e.getMessage());
363
      } catch (IOException ioe) {
364
        System.err.println("Error printing qspec:");
365
        System.err.println(ioe.getMessage());
366
      }
367
    //System.out.println("docListResult: ");
368
    //System.out.println(docListResult.toString());
369
    return docListResult;
370
  }
371
  
372
  /**
373
   * returns a string array of the contents of a particular node. 
374
   * If the node appears more than once, the contents are returned 
375
   * in the order in which they appearred in the document.
376
   * @param nodename the name or path of the particular node.
377
   * @param docid the docid of the document you want the node from.
378
   * @param conn a database connection-this allows this method to be static
379
   */
380
  public static Object[] getNodeContent(String nodename, String docid, 
381
                                        Connection conn)
382
  {
383
    StringBuffer query = new StringBuffer();
384
    Vector result = new Vector();
385
    PreparedStatement pstmt;
386
    query.append("select nodedata from xml_nodes where parentnodeid in ");
387
    query.append("(select nodeid from xml_index where path like '");
388
    query.append(nodename);
389
    query.append("' and docid like '").append(docid).append("')");
390
    try
391
    {
392
      pstmt = conn.prepareStatement(query.toString());
393

    
394
      // Execute the SQL query using the JDBC connection
395
      pstmt.execute();
396
      ResultSet rs = pstmt.getResultSet();
397
      boolean tableHasRows = rs.next();
398
      while (tableHasRows) 
399
      {
400
        result.add(rs.getString(1));
401
        System.out.println(rs.getString(1));
402
        tableHasRows = rs.next();
403
      }
404
    } 
405
    catch (SQLException e) 
406
    {
407
      System.err.println("Error getting id: " + e.getMessage());
408
    } 
409
    
410
    return result.toArray();
411
  }
412
  
413
  /**
414
   * format a structured query as an XML document that conforms
415
   * to the pathquery.dtd and is appropriate for submission to the DBQuery
416
   * structured query engine
417
   *
418
   * @param params The list of parameters that  should be included in the query
419
   */
420
  public static String createSQuery(Hashtable params)
421
  { 
422
    StringBuffer query = new StringBuffer();
423
    Enumeration elements;
424
    Enumeration keys;
425
    String doctype = null;
426
    String casesensitive = null;
427
    String searchmode = null;
428
    Object nextkey;
429
    Object nextelement;
430
    //add the xml headers
431
    query.append("<?xml version=\"1.0\"?>\n");
432
    query.append("<pathquery version=\"1.0\"><meta_file_id>");
433
    
434
    if(params.containsKey("meta_file_id"))
435
    {
436
      query.append( ((String[])params.get("meta_file_id"))[0]);
437
      query.append("</meta_file_id>");
438
    }
439
    else
440
    {
441
      query.append("unspecified</meta_file_id>");
442
    }
443
    
444
    query.append("<querytitle>");
445
    if(params.containsKey("querytitle"))
446
    {
447
      query.append(((String[])params.get("querytitle"))[0]);
448
      query.append("</querytitle>");
449
    }
450
    else
451
    {
452
      query.append("unspecified</querytitle>");
453
    }
454
    
455
    if(params.containsKey("doctype"))
456
    {
457
      doctype = ((String[])params.get("doctype"))[0]; 
458
    }
459
    else
460
    {
461
      doctype = "ANY";  
462
    }
463
    
464
    if(params.containsKey("returnfield"))
465
    {
466
      String[] returnfield = ((String[])params.get("returnfield"));
467
      for(int i=0; i<returnfield.length; i++)
468
      {
469
        query.append("<returnfield>").append(returnfield[i]);
470
        query.append("</returnfield>");
471
      }
472
    }
473
    
474
    if(params.containsKey("owner"))
475
    {
476
      String[] owner = ((String[])params.get("owner"));
477
      for(int i=0; i<owner.length; i++)
478
      {
479
        query.append("<owner>").append(owner[i]);
480
        query.append("</owner>");
481
      }
482
    }
483
    
484
    if(params.containsKey("site"))
485
    {
486
      String[] site = ((String[])params.get("site"));
487
      for(int i=0; i<site.length; i++)
488
      {
489
        query.append("<site>").append(site[i]);
490
        query.append("</site>");
491
      }
492
    }
493
    
494
    //if you don't limit the query by doctype, then it just creates
495
    //an empty returndoctype tag.
496
    if (!doctype.equals("any") && 
497
        !doctype.equals("ANY") &&
498
        !doctype.equals("") ) 
499
    {
500
       query.append("<returndoctype>");
501
       query.append(doctype).append("</returndoctype>");
502
    }
503
    else
504
    { 
505
      query.append("<returndoctype></returndoctype>");
506
    }
507
    
508
    //allows the dynamic switching of boolean operators
509
    if(params.containsKey("operator"))
510
    {
511
      query.append("<querygroup operator=\"" + 
512
                ((String[])params.get("operator"))[0] + "\">");
513
    }
514
    else
515
    { //the default operator is UNION
516
      query.append("<querygroup operator=\"UNION\">"); 
517
    }
518
        
519
    if(params.containsKey("casesensitive"))
520
    {
521
      casesensitive = ((String[])params.get("casesensitive"))[0]; 
522
    }
523
    else
524
    {
525
      casesensitive = "false"; 
526
    }
527
    
528
    if(params.containsKey("searchmode"))
529
    {
530
      searchmode = ((String[])params.get("searchmode"))[0]; 
531
    }
532
    else
533
    {
534
      searchmode = "contains"; 
535
    }
536
        
537
    //anyfield is a special case because it does a 
538
    //free text search.  It does not have a <pathexpr>
539
    //tag.  This allows for a free text search within the structured
540
    //query.  This is useful if the INTERSECT operator is used.
541
    if(params.containsKey("anyfield"))
542
    {
543
       String[] anyfield = ((String[])params.get("anyfield"));
544
       //allow for more than one value for anyfield
545
       for(int i=0; i<anyfield.length; i++)
546
       {
547
         if(!anyfield[i].equals(""))
548
         {
549
           query.append("<queryterm casesensitive=\"" + casesensitive + 
550
                        "\" " + "searchmode=\"" + searchmode + "\"><value>" +
551
                        anyfield[i] +
552
                        "</value></queryterm>"); 
553
         }
554
       }
555
    }
556
        
557
    //this while loop finds the rest of the parameters
558
    //and attempts to query for the field specified
559
    //by the parameter.
560
    elements = params.elements();
561
    keys = params.keys();
562
    while(keys.hasMoreElements() && elements.hasMoreElements())
563
    {
564
      nextkey = keys.nextElement();
565
      nextelement = elements.nextElement();
566

    
567
      //make sure we aren't querying for any of these
568
      //parameters since the are already in the query
569
      //in one form or another.
570
      if(!nextkey.toString().equals("doctype") && 
571
         !nextkey.toString().equals("action")  &&
572
         !nextkey.toString().equals("qformat") && 
573
         !nextkey.toString().equals("anyfield") &&
574
         !nextkey.toString().equals("returnfield") &&
575
         !nextkey.toString().equals("owner") &&
576
         !nextkey.toString().equals("site") &&
577
         !nextkey.toString().equals("operator") )
578
      {
579
        //allow for more than value per field name
580
        for(int i=0; i<((String[])nextelement).length; i++)
581
        {
582
          if(!((String[])nextelement)[i].equals(""))
583
          {
584
            query.append("<queryterm casesensitive=\"" + casesensitive +"\" " + 
585
                         "searchmode=\"" + searchmode + "\">" +
586
                         "<value>" +
587
                         //add the query value
588
                         ((String[])nextelement)[i] +
589
                         "</value><pathexpr>" +
590
                         //add the path to query by 
591
                         nextkey.toString() + 
592
                         "</pathexpr></queryterm>");
593
          }
594
        }
595
      }
596
    }
597
    query.append("</querygroup></pathquery>");
598
    //append on the end of the xml and return the result as a string
599
    return query.toString();
600
  }
601
  
602
  /**
603
   * format a simple free-text value query as an XML document that conforms
604
   * to the pathquery.dtd and is appropriate for submission to the DBQuery
605
   * structured query engine
606
   *
607
   * @param value the text string to search for in the xml catalog
608
   * @param doctype the type of documents to include in the result set -- use
609
   *        "any" or "ANY" for unfiltered result sets
610
   */
611
   public static String createQuery(String value, String doctype) {
612
     StringBuffer xmlquery = new StringBuffer();
613
     xmlquery.append("<?xml version=\"1.0\"?>\n");
614
     xmlquery.append("<pathquery version=\"1.0\">");
615
     xmlquery.append("<meta_file_id>Unspecified</meta_file_id>");
616
     xmlquery.append("<querytitle>Unspecified</querytitle>");
617

    
618
     if (!doctype.equals("any") && !doctype.equals("ANY")) {
619
       xmlquery.append("<returndoctype>");
620
       xmlquery.append(doctype).append("</returndoctype>");
621
     }
622

    
623
     xmlquery.append("<querygroup operator=\"UNION\">");
624
     //chad added - 8/14
625
     //the if statement allows a query to gracefully handle a null 
626
     //query.  Without this if a nullpointerException is thrown.
627
     if(!value.equals(""))
628
     {
629
       xmlquery.append("<queryterm casesensitive=\"false\" ");
630
       xmlquery.append("searchmode=\"contains\">");
631
       xmlquery.append("<value>").append(value).append("</value>");
632
       xmlquery.append("</queryterm>");
633
     }
634
     xmlquery.append("</querygroup>");
635
     xmlquery.append("</pathquery>");
636

    
637
     
638
     return (xmlquery.toString());
639
   }
640

    
641
  /**
642
   * format a simple free-text value query as an XML document that conforms
643
   * to the pathquery.dtd and is appropriate for submission to the DBQuery
644
   * structured query engine
645
   *
646
   * @param value the text string to search for in the xml catalog
647
   */
648
   public static String createQuery(String value) {
649
     return createQuery(value, "any");
650
   }
651
   
652
  /** 
653
    * Check for "READ" permission on @docid for @user and/or @group 
654
    * from DB connection 
655
    */
656
  private boolean hasPermission ( Connection conn, String user,
657
                                  String group, String docid ) 
658
                  throws SQLException
659
  {
660
    // b' of the command line invocation
661
    if ( (user == null) && (group == null) ) {
662
      return true;
663
    }
664
    
665
    // Check for READ permission on @docid for @user and/or @group
666
    AccessControlList aclobj = new AccessControlList(conn);
667
    boolean hasPermission = aclobj.hasPermission("READ",user,docid);
668
    if ( !hasPermission && group != null ) {
669
      hasPermission = aclobj.hasPermission("READ",group,docid);
670
    }
671
    
672
    return hasPermission;
673
  }
674
   
675
}
(14-14/39)