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 09:28:06 -0800 (Wed, 20 Dec 2000) $'
15
 * '$Revision: 624 $'
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
            
207
            pstmt = conn.prepareStatement(btBuf.toString());
208
            pstmt.execute();
209
            ResultSet btrs = pstmt.getResultSet();
210
            boolean hasBtRows = btrs.next();
211
            if(hasBtRows)
212
            { //there was a backtrackable document found
213
              DocumentImpl xmldoc = null;
214
              //System.out.println("document found is: " + btrs.getString(1));
215
              MetacatURL objURL = new MetacatURL(btrs.getString(1));
216
              try
217
              {
218
                xmldoc = new DocumentImpl(conn, objURL.getParam(0)[1]);
219
              }
220
              catch(Exception e)
221
              {
222
                System.out.println("Error getting document: " + e.getMessage());
223
              }
224
              
225
              docid   = xmldoc.getDocID();
226
              docname = xmldoc.getDocname();
227
              doctype = xmldoc.getDoctype();
228
              doctitle = xmldoc.getDocTitle();
229
              createDate = xmldoc.getCreateDate();
230
              updateDate = xmldoc.getUpdateDate();
231
              //System.out.println("docname: " + docname + " doctype: " + doctype + 
232
              //                   " doctitle: " + doctitle + " createdate: " +
233
              //                   createDate + " updatedate: " + updateDate);
234
            }
235
            btrs.close();
236
          }
237
          
238
          document = new StringBuffer();
239
          //System.out.println("packagdoctype: " + util.getOption("packagedoctype"));
240
          //if(!doctype.equals(util.getOption("packagedoctype")))
241
          {
242
            String completeDocid = docid + util.getOption("accNumSeparator");
243
            completeDocid += rev;
244
            document.append("<docid>").append(completeDocid).append("</docid>");
245
            if (docname != null) {
246
              document.append("<docname>" + docname + "</docname>");
247
            }
248
            if (doctype != null) {
249
              document.append("<doctype>" + doctype + "</doctype>");
250
            }
251
            if (doctitle != null) {
252
              document.append("<doctitle>" + doctitle + "</doctitle>");
253
            }
254
            if(createDate != null) {
255
              document.append("<createdate>" + createDate + "</createdate>");
256
            }
257
            if(updateDate != null) {
258
              document.append("<updatedate>" + updateDate + "</updatedate>");
259
            }
260
            // Store the document id and the root node id
261
            docListResult.put(docid,(String)document.toString());
262
          }
263

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

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

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

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

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

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

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

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

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

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