Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that represents a structured query, and can be 
4
 *             constructed from an XML serialization conforming to 
5
 *             pathquery.dtd. The printSQL() method can be used to print 
6
 *             a SQL serialization of the query.
7
 *  Copyright: 2000 Regents of the University of California and the
8
 *             National Center for Ecological Analysis and Synthesis
9
 *    Authors: Matt Jones
10
 *    Release: @release@
11
 *
12
 *   '$Author: jones $'
13
 *     '$Date: 2000-08-14 13:53:34 -0700 (Mon, 14 Aug 2000) $'
14
 * '$Revision: 349 $'
15
 */
16

    
17
package edu.ucsb.nceas.metacat;
18

    
19
import java.io.*;
20
import java.util.Stack;
21
import java.util.Vector;
22
import java.util.Enumeration;
23

    
24
import org.xml.sax.Attributes;
25
import org.xml.sax.InputSource;
26
import org.xml.sax.SAXException;
27
import org.xml.sax.SAXParseException;
28
import org.xml.sax.XMLReader;
29
import org.xml.sax.helpers.XMLReaderFactory;
30
import org.xml.sax.helpers.DefaultHandler;
31

    
32
/** 
33
 * A Class that represents a structured query, and can be 
34
 * constructed from an XML serialization conforming to @see pathquery.dtd. 
35
 * The printSQL() method can be used to print a SQL serialization of the query.
36
 */
37
public class QuerySpecification extends DefaultHandler {
38
 
39
  // Query data structures
40
  private String meta_file_id;
41
  private String querytitle;
42
  private Vector doctypeList;
43
  private QueryGroup query = null;
44

    
45
  private Stack elementStack;
46
  private Stack queryStack;
47
  private String currentValue;
48
  private String currentPathexpr;
49
  private String parserName = null;
50

    
51
  /**
52
   * construct an instance of the QuerySpecification class 
53
   *
54
   * @param queryspec the XML representation of the query (should conform
55
   *                  to pathquery.dtd) as a Reader
56
   * @param parserName the fully qualified name of a Java Class implementing
57
   *                  the org.xml.sax.XMLReader interface
58
   */
59
  public QuerySpecification( Reader queryspec, String parserName ) 
60
         throws IOException {
61
    super();
62

    
63
    // Initialize the class variables
64
    doctypeList = new Vector();
65
    elementStack = new Stack();
66
    queryStack   = new Stack();
67
    this.parserName = parserName;
68

    
69
    // Initialize the parser and read the queryspec
70
    XMLReader parser = initializeParser();
71
    if (parser == null) {
72
      System.err.println("SAX parser not instantiated properly.");
73
    }
74
    try {
75
      parser.parse(new InputSource(queryspec));
76
    } catch (SAXException e) {
77
      System.err.println("error parsing data");
78
      System.err.println(e.getMessage());
79
    }
80
  }
81

    
82
  /**
83
   * construct an instance of the QuerySpecification class 
84
   *
85
   * @param queryspec the XML representation of the query (should conform
86
   *                  to pathquery.dtd) as a String
87
   * @param parserName the fully qualified name of a Java Class implementing
88
   *                  the org.xml.sax.Parser interface
89
   */
90
  public QuerySpecification( String queryspec, String parserName ) 
91
         throws IOException {
92
    this(new StringReader(queryspec), parserName);
93
  }
94

    
95
  /** Main routine for testing */
96
  static public void main(String[] args) {
97

    
98
     if (args.length < 1) {
99
       System.err.println("Wrong number of arguments!!!");
100
       System.err.println("USAGE: java QuerySpecification <xmlfile>");
101
       return;
102
     } else {
103
       String xmlfile  = args[0];
104
        
105
       try {
106
         MetaCatUtil util = new MetaCatUtil();
107
         FileReader xml = new FileReader(new File(xmlfile));
108
         QuerySpecification qspec = 
109
                 new QuerySpecification(xml, util.getOption("saxparser"));
110
         System.out.println(qspec.printSQL());
111

    
112
       } catch (IOException e) {
113
         System.err.println(e.getMessage());
114
       }
115
         
116
     }
117
  }
118

    
119
  /**
120
   * Set up the SAX parser for reading the XML serialized query
121
   */
122
  private XMLReader initializeParser() {
123
    XMLReader parser = null;
124

    
125
    // Set up the SAX document handlers for parsing
126
    try {
127

    
128
      // Get an instance of the parser
129
      parser = XMLReaderFactory.createXMLReader(parserName);
130

    
131
      // Set the ContentHandler to this instance
132
      parser.setContentHandler(this);
133

    
134
      // Set the error Handler to this instance
135
      parser.setErrorHandler(this);
136

    
137
    } catch (Exception e) {
138
       System.err.println(e.toString());
139
    }
140

    
141
    return parser;
142
  }
143

    
144
  /**
145
   * callback method used by the SAX Parser when the start tag of an 
146
   * element is detected. Used in this context to parse and store
147
   * the query information in class variables.
148
   */
149
  public void startElement (String uri, String localName, 
150
                            String qName, Attributes atts) 
151
         throws SAXException {
152
    BasicNode currentNode = new BasicNode(localName);
153
    // add attributes to BasicNode here
154
    if (atts != null) {
155
      int len = atts.getLength();
156
      for (int i = 0; i < len; i++) {
157
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
158
      }
159
    }
160

    
161
    elementStack.push(currentNode); 
162
    if (currentNode.getTagName().equals("querygroup")) {
163
      QueryGroup currentGroup = new QueryGroup(
164
                                currentNode.getAttribute("operator"));
165
      if (query == null) {
166
        query = currentGroup;
167
      } else {
168
        QueryGroup parentGroup = (QueryGroup)queryStack.peek();
169
        parentGroup.addChild(currentGroup);
170
      }
171
      queryStack.push(currentGroup);
172
    }
173
  }
174

    
175
  /**
176
   * callback method used by the SAX Parser when the end tag of an 
177
   * element is detected. Used in this context to parse and store
178
   * the query information in class variables.
179
   */
180
  public void endElement (String uri, String localName,
181
                          String qName) throws SAXException {
182
    BasicNode leaving = (BasicNode)elementStack.pop(); 
183
    if (leaving.getTagName().equals("queryterm")) {
184
      boolean isCaseSensitive = (new Boolean(
185
              leaving.getAttribute("casesensitive"))).booleanValue();
186
      QueryTerm currentTerm = null;
187
      if (currentPathexpr == null) {
188
        currentTerm = new QueryTerm(isCaseSensitive,
189
                      leaving.getAttribute("searchmode"),currentValue);
190
      } else {
191
        currentTerm = new QueryTerm(isCaseSensitive,
192
                      leaving.getAttribute("searchmode"),currentValue,
193
                      currentPathexpr);
194
      }
195
      QueryGroup currentGroup = (QueryGroup)queryStack.peek();
196
      currentGroup.addChild(currentTerm);
197
      currentValue = null;
198
      currentPathexpr = null;
199
    } else if (leaving.getTagName().equals("querygroup")) {
200
      QueryGroup leavingGroup = (QueryGroup)queryStack.pop();
201
    }
202
  }
203

    
204
  /**
205
   * callback method used by the SAX Parser when the text sequences of an 
206
   * xml stream are detected. Used in this context to parse and store
207
   * the query information in class variables.
208
   */
209
  public void characters(char ch[], int start, int length) {
210

    
211
    String inputString = new String(ch, start, length);
212
    BasicNode currentNode = (BasicNode)elementStack.peek(); 
213
    String currentTag = currentNode.getTagName();
214
    if (currentTag.equals("meta_file_id")) {
215
      meta_file_id = inputString;
216
    } else if (currentTag.equals("querytitle")) {
217
      querytitle = inputString;
218
    } else if (currentTag.equals("value")) {
219
      currentValue = inputString;
220
    } else if (currentTag.equals("pathexpr")) {
221
      currentPathexpr = inputString;
222
    } else if (currentTag.equals("returndoctype")) {
223
      doctypeList.add(inputString);
224
    }
225
  }
226

    
227

    
228
  /**
229
   * create a SQL serialization of the query that this instance represents
230
   */
231
  public String printSQL() {
232
    StringBuffer self = new StringBuffer();
233

    
234
    // This determines the returned results
235
    self.append("SELECT docid,docname,doctype,doctitle ");
236
    self.append("FROM xml_documents WHERE docid IN (");
237

    
238
    // This determines the documents that meet the query conditions
239
    self.append(query.printSQL());
240

    
241
    self.append(") ");
242

    
243
    // Add SQL to filter for doctypes requested in the query
244
    if (!doctypeList.isEmpty()) {
245
      boolean firstdoctype = true;
246
      self.append(" AND ("); 
247
      Enumeration en = doctypeList.elements();
248
      while (en.hasMoreElements()) {
249
        String currentDoctype = (String)en.nextElement();
250
        if (firstdoctype) {
251
          firstdoctype = false;
252
          self.append(" doctype = '" + currentDoctype + "'"); 
253
        } else {
254
          self.append(" OR doctype = '" + currentDoctype + "'"); 
255
        }
256
      }
257
      self.append(") ");
258
    }
259

    
260
    return self.toString();
261
  }
262

    
263
  /**
264
   * create a String description of the query that this instance represents.
265
   * This should become a way to get the XML serialization of the query.
266
   */
267
  public String toString() {
268
    return "meta_file_id=" + meta_file_id + "\n" + 
269
           "querytitle=" + querytitle + "\n" + query;
270
  }
271

    
272
  /** a utility class that represents a group of terms in a query */
273
  private class QueryGroup {
274
    private String operator = null;  // indicates how query terms are combined
275
    private Vector children = null;  // the list of query terms and groups
276

    
277
    /** 
278
     * construct a new QueryGroup 
279
     *
280
     * @param operator the boolean conector used to connect query terms 
281
     *                    in this query group
282
     */
283
    public QueryGroup(String operator) {
284
      this.operator = operator;
285
      children = new Vector();
286
    }
287

    
288
    /** 
289
     * Add a child QueryGroup to this QueryGroup
290
     *
291
     * @param qgroup the query group to be added to the list of terms
292
     */
293
    public void addChild(QueryGroup qgroup) {
294
      children.add((Object)qgroup); 
295
    }
296

    
297
    /**
298
     * Add a child QueryTerm to this QueryGroup
299
     *
300
     * @param qterm the query term to be added to the list of terms
301
     */
302
    public void addChild(QueryTerm qterm) {
303
      children.add((Object)qterm); 
304
    }
305

    
306
    /**
307
     * Retrieve an Enumeration of query terms for this QueryGroup
308
     */
309
    public Enumeration getChildren() {
310
      return children.elements();
311
    }
312
   
313
    /**
314
     * create a SQL serialization of the query that this instance represents
315
     */
316
    public String printSQL() {
317
      StringBuffer self = new StringBuffer();
318
      boolean first = true;
319

    
320
      self.append("(");
321

    
322
      Enumeration en= getChildren();
323
      while (en.hasMoreElements()) {
324
        Object qobject = en.nextElement();
325
        if (first) {
326
          first = false;
327
        } else {
328
          self.append(" " + operator + " ");
329
        }
330
        if (qobject instanceof QueryGroup) {
331
          QueryGroup qg = (QueryGroup)qobject;
332
          self.append(qg.printSQL());
333
        } else if (qobject instanceof QueryTerm) {
334
          QueryTerm qt = (QueryTerm)qobject;
335
          self.append(qt.printSQL());
336
        } else {
337
          System.err.println("qobject wrong type: fatal error");
338
        }
339
      }
340
      self.append(") \n");
341
      return self.toString();
342
    }
343

    
344
    /**
345
     * create a String description of the query that this instance represents.
346
     * This should become a way to get the XML serialization of the query.
347
     */
348
    public String toString() {
349
      StringBuffer self = new StringBuffer();
350

    
351
      self.append("  (Query group operator=" + operator + "\n");
352
      Enumeration en= getChildren();
353
      while (en.hasMoreElements()) {
354
        Object qobject = en.nextElement();
355
        self.append(qobject);
356
      }
357
      self.append("  )\n");
358
      return self.toString();
359
    }
360
  }
361

    
362
  /** a utility class that represents a single term in a query */
363
  private class QueryTerm {
364
    private boolean casesensitive = false;
365
    private String searchmode = null;
366
    private String value = null;
367
    private String pathexpr = null;
368

    
369
    /**
370
     * Construct a new instance of a query term for a free text search
371
     * (using the value only)
372
     *
373
     * @param casesensitive flag indicating whether case is used to match
374
     * @param searchmode determines what kind of substring match is performed
375
     *        (one of starts-with|ends-with|contains|matches-exactly)
376
     * @param value the text value to match
377
     */
378
    public QueryTerm(boolean casesensitive, String searchmode, 
379
                     String value) {
380
      this.casesensitive = casesensitive;
381
      this.searchmode = searchmode;
382
      this.value = value;
383
    }
384

    
385
    /**
386
     * Construct a new instance of a query term for a structured search
387
     * (matching the value only for those nodes in the pathexpr)
388
     *
389
     * @param casesensitive flag indicating whether case is used to match
390
     * @param searchmode determines what kind of substring match is performed
391
     *        (one of starts-with|ends-with|contains|matches-exactly)
392
     * @param value the text value to match
393
     * @param pathexpr the hierarchical path to the nodes to be searched
394
     */
395
    public QueryTerm(boolean casesensitive, String searchmode, 
396
                     String value, String pathexpr) {
397
      this(casesensitive, searchmode, value);
398
      this.pathexpr = pathexpr;
399
    }
400

    
401
    /** determine if the QueryTerm is case sensitive */
402
    public boolean isCaseSensitive() {
403
      return casesensitive;
404
    }
405

    
406
    /** get the searchmode parameter */
407
    public String getSearchMode() {
408
      return searchmode;
409
    }
410
 
411
    /** get the Value parameter */
412
    public String getValue() {
413
      return value;
414
    }
415

    
416
    /** get the path expression parameter */
417
    public String getPathExpression() {
418
      return pathexpr;
419
    }
420

    
421
    /**
422
     * create a SQL serialization of the query that this instance represents
423
     */
424
    public String printSQL() {
425
      StringBuffer self = new StringBuffer();
426

    
427
      // Uppercase the search string if case match is not important
428
      String casevalue = null;
429
      String nodedataterm = null;
430

    
431
      if (casesensitive) {
432
        nodedataterm = "nodedata";
433
        casevalue = value;
434
      } else {
435
        nodedataterm = "UPPER(nodedata)";
436
        casevalue = value.toUpperCase();
437
      }
438

    
439
      // Add appropriate wildcards to search string
440
      String searchvalue = null;
441
      if (searchmode.equals("starts-with")) {
442
        searchvalue = casevalue + "%";
443
      } else if (searchmode.equals("ends-with")) {
444
        searchvalue = "%" + casevalue;
445
      } else if (searchmode.equals("contains")) {
446
        searchvalue = "%" + casevalue + "%";
447
      } else {
448
        searchvalue = casevalue;
449
      }
450

    
451
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
452

    
453
      if (pathexpr != null) {
454
        self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
455
        self.append("AND parentnodeid IN ");
456
        self.append("(SELECT nodeid FROM xml_index WHERE path LIKE " + 
457
                    "'" +  pathexpr + "') " );
458
      } else {
459
        self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
460
      }
461

    
462
      return self.toString();
463
    }
464

    
465
    /**
466
     * create a String description of the query that this instance represents.
467
     * This should become a way to get the XML serialization of the query.
468
     */
469
    public String toString() {
470
      StringBuffer self = new StringBuffer();
471

    
472
      self.append("    Query Term iscasesensitive=" + casesensitive + "\n");
473
      self.append("               searchmode=" + searchmode + "\n");
474
      self.append("               value=" + value + "\n");
475
      if (pathexpr != null) {
476
        self.append("               pathexpr=" + pathexpr + "\n");
477
      }
478

    
479
      return self.toString();
480
    }
481
  }
482
}
483

    
484
/**
485
 * '$Log$
486
 * 'Revision 1.10  2000/06/26 10:35:05  jones
487
 * 'Merged in substantial changes to DBWriter and associated classes and to
488
 * 'the MetaCatServlet in order to accomodate the new UPDATE and DELETE
489
 * 'functions.  The command line tools and the parameters for the
490
 * 'servlet have changed substantially.
491
 * '
492
 * 'Revision 1.9.2.3  2000/06/25 23:38:17  jones
493
 * 'Added RCSfile keyword
494
 * '
495
 * 'Revision 1.9.2.2  2000/06/25 23:34:18  jones
496
 * 'Changed documentation formatting, added log entries at bottom of source files
497
 * ''
498
 */
(25-25/27)