Project

General

Profile

1 155 jones
/**
2 203 jones
 *  '$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 155 jones
 *
11 203 jones
 *   '$Author$'
12
 *     '$Date$'
13
 * '$Revision$'
14 155 jones
 */
15
16
package edu.ucsb.nceas.metacat;
17
18
import java.io.*;
19
import java.util.Stack;
20 158 jones
import java.util.Vector;
21 159 jones
import java.util.Enumeration;
22 155 jones
23 185 jones
import org.xml.sax.Attributes;
24 158 jones
import org.xml.sax.InputSource;
25
import org.xml.sax.SAXException;
26
import org.xml.sax.SAXParseException;
27 185 jones
import org.xml.sax.XMLReader;
28
import org.xml.sax.helpers.XMLReaderFactory;
29
import org.xml.sax.helpers.DefaultHandler;
30 155 jones
31
/**
32 172 jones
 * A Class that represents a structured query, and can be
33
 * constructed from an XML serialization conforming to @see pathquery.dtd.
34
 * The printSQL() method can be used to print a SQL serialization of the query.
35 155 jones
 */
36 185 jones
public class QuerySpecification extends DefaultHandler {
37 155 jones
38 158 jones
  // Query data structures
39
  private String meta_file_id;
40
  private String querytitle;
41 172 jones
  private Vector doctypeList;
42 158 jones
  private QueryGroup query = null;
43
44
  private Stack elementStack;
45
  private Stack queryStack;
46 159 jones
  private String currentValue;
47
  private String currentPathexpr;
48 172 jones
  private String parserName = null;
49 158 jones
50 155 jones
  /**
51
   * construct an instance of the QuerySpecification class
52
   *
53 172 jones
   * @param queryspec the XML representation of the query (should conform
54
   *                  to pathquery.dtd) as a Reader
55
   * @param parserName the fully qualified name of a Java Class implementing
56 185 jones
   *                  the org.xml.sax.XMLReader interface
57 155 jones
   */
58 172 jones
  public QuerySpecification( Reader queryspec, String parserName )
59
         throws IOException {
60 155 jones
    super();
61 158 jones
62 172 jones
    // Initialize the class variables
63
    doctypeList = new Vector();
64 158 jones
    elementStack = new Stack();
65
    queryStack   = new Stack();
66 172 jones
    this.parserName = parserName;
67 158 jones
68
    // Initialize the parser and read the queryspec
69 185 jones
    XMLReader parser = initializeParser();
70 181 jones
    if (parser == null) {
71
      System.err.println("SAX parser not instantiated properly.");
72
    }
73 155 jones
    try {
74
      parser.parse(new InputSource(queryspec));
75
    } catch (SAXException e) {
76 180 jones
      System.err.println("error parsing data");
77
      System.err.println(e.getMessage());
78 155 jones
    }
79
  }
80
81
  /**
82
   * construct an instance of the QuerySpecification class
83
   *
84 172 jones
   * @param queryspec the XML representation of the query (should conform
85
   *                  to pathquery.dtd) as a String
86
   * @param parserName the fully qualified name of a Java Class implementing
87
   *                  the org.xml.sax.Parser interface
88 155 jones
   */
89 172 jones
  public QuerySpecification( String queryspec, String parserName )
90
         throws IOException {
91
    this(new StringReader(queryspec), parserName);
92 155 jones
  }
93
94
  /** Main routine for testing */
95
  static public void main(String[] args) {
96
97
     if (args.length < 1) {
98
       System.err.println("Wrong number of arguments!!!");
99
       System.err.println("USAGE: java QuerySpecification <xmlfile>");
100
       return;
101
     } else {
102
       String xmlfile  = args[0];
103
104
       try {
105 203 jones
         MetaCatUtil util = new MetaCatUtil();
106 155 jones
         FileReader xml = new FileReader(new File(xmlfile));
107 203 jones
         QuerySpecification qspec =
108
                 new QuerySpecification(xml, util.getOption("saxparser"));
109 172 jones
         System.out.println(qspec.printSQL());
110 181 jones
111 155 jones
       } catch (IOException e) {
112
         System.err.println(e.getMessage());
113
       }
114
115
     }
116
  }
117
118 172 jones
  /**
119
   * Set up the SAX parser for reading the XML serialized query
120
   */
121 185 jones
  private XMLReader initializeParser() {
122
    XMLReader parser = null;
123 172 jones
124 155 jones
    // Set up the SAX document handlers for parsing
125
    try {
126
127
      // Get an instance of the parser
128 185 jones
      parser = XMLReaderFactory.createXMLReader(parserName);
129 155 jones
130 185 jones
      // Set the ContentHandler to this instance
131
      parser.setContentHandler(this);
132 155 jones
133 185 jones
      // Set the error Handler to this instance
134 158 jones
      parser.setErrorHandler(this);
135 155 jones
136
    } catch (Exception e) {
137
       System.err.println(e.toString());
138
    }
139
140
    return parser;
141
  }
142
143 172 jones
  /**
144
   * callback method used by the SAX Parser when the start tag of an
145
   * element is detected. Used in this context to parse and store
146
   * the query information in class variables.
147
   */
148 185 jones
  public void startElement (String uri, String localName,
149
                            String qName, Attributes atts)
150 155 jones
         throws SAXException {
151 185 jones
    BasicNode currentNode = new BasicNode(localName);
152 159 jones
    // add attributes to BasicNode here
153
    if (atts != null) {
154
      int len = atts.getLength();
155
      for (int i = 0; i < len; i++) {
156 185 jones
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
157 159 jones
      }
158
    }
159
160 158 jones
    elementStack.push(currentNode);
161 159 jones
    if (currentNode.getTagName().equals("querygroup")) {
162 158 jones
      QueryGroup currentGroup = new QueryGroup(
163 178 jones
                                currentNode.getAttribute("operator"));
164 158 jones
      if (query == null) {
165
        query = currentGroup;
166 159 jones
      } else {
167
        QueryGroup parentGroup = (QueryGroup)queryStack.peek();
168
        parentGroup.addChild(currentGroup);
169 158 jones
      }
170 159 jones
      queryStack.push(currentGroup);
171 158 jones
    }
172 155 jones
  }
173
174 172 jones
  /**
175
   * callback method used by the SAX Parser when the end tag of an
176
   * element is detected. Used in this context to parse and store
177
   * the query information in class variables.
178
   */
179 185 jones
  public void endElement (String uri, String localName,
180
                          String qName) throws SAXException {
181 158 jones
    BasicNode leaving = (BasicNode)elementStack.pop();
182 159 jones
    if (leaving.getTagName().equals("queryterm")) {
183
      boolean isCaseSensitive = (new Boolean(
184
              leaving.getAttribute("casesensitive"))).booleanValue();
185
      QueryTerm currentTerm = null;
186
      if (currentPathexpr == null) {
187
        currentTerm = new QueryTerm(isCaseSensitive,
188
                      leaving.getAttribute("searchmode"),currentValue);
189
      } else {
190
        currentTerm = new QueryTerm(isCaseSensitive,
191
                      leaving.getAttribute("searchmode"),currentValue,
192
                      currentPathexpr);
193
      }
194
      QueryGroup currentGroup = (QueryGroup)queryStack.peek();
195
      currentGroup.addChild(currentTerm);
196
      currentValue = null;
197
      currentPathexpr = null;
198
    } else if (leaving.getTagName().equals("querygroup")) {
199
      QueryGroup leavingGroup = (QueryGroup)queryStack.pop();
200 158 jones
    }
201 155 jones
  }
202 158 jones
203 172 jones
  /**
204
   * callback method used by the SAX Parser when the text sequences of an
205
   * xml stream are detected. Used in this context to parse and store
206
   * the query information in class variables.
207
   */
208 158 jones
  public void characters(char ch[], int start, int length) {
209
210
    String inputString = new String(ch, start, length);
211
    BasicNode currentNode = (BasicNode)elementStack.peek();
212
    String currentTag = currentNode.getTagName();
213
    if (currentTag.equals("meta_file_id")) {
214
      meta_file_id = inputString;
215
    } else if (currentTag.equals("querytitle")) {
216
      querytitle = inputString;
217 159 jones
    } else if (currentTag.equals("value")) {
218
      currentValue = inputString;
219
    } else if (currentTag.equals("pathexpr")) {
220
      currentPathexpr = inputString;
221 172 jones
    } else if (currentTag.equals("returndoctype")) {
222
      doctypeList.add(inputString);
223 158 jones
    }
224
  }
225
226 172 jones
227
  /**
228
   * create a SQL serialization of the query that this instance represents
229
   */
230
  public String printSQL() {
231 170 jones
    StringBuffer self = new StringBuffer();
232
233 172 jones
    // This determines the returned results
234
    self.append("SELECT docid,docname,doctype,doctitle ");
235
    self.append("FROM xml_documents WHERE docid IN (");
236 170 jones
237 178 jones
    // This determines the documents that meet the query conditions
238 172 jones
    self.append(query.printSQL());
239
240
    self.append(") ");
241
242
    // Add SQL to filter for doctypes requested in the query
243
    if (!doctypeList.isEmpty()) {
244
      boolean firstdoctype = true;
245
      self.append(" AND (");
246
      Enumeration en = doctypeList.elements();
247
      while (en.hasMoreElements()) {
248
        String currentDoctype = (String)en.nextElement();
249
        if (firstdoctype) {
250
          firstdoctype = false;
251
          self.append(" doctype = '" + currentDoctype + "'");
252
        } else {
253
          self.append(" OR doctype = '" + currentDoctype + "'");
254
        }
255
      }
256
      self.append(") ");
257
    }
258
259 170 jones
    return self.toString();
260
  }
261
262 172 jones
  /**
263
   * create a String description of the query that this instance represents.
264
   * This should become a way to get the XML serialization of the query.
265
   */
266 159 jones
  public String toString() {
267
    return "meta_file_id=" + meta_file_id + "\n" +
268
           "querytitle=" + querytitle + "\n" + query;
269
  }
270
271 158 jones
  /** a utility class that represents a group of terms in a query */
272
  private class QueryGroup {
273 178 jones
    private String operator = null;  // indicates how query terms are combined
274
    private Vector children = null;  // the list of query terms and groups
275 158 jones
276 172 jones
    /**
277
     * construct a new QueryGroup
278
     *
279 178 jones
     * @param operator the boolean conector used to connect query terms
280 172 jones
     *                    in this query group
281
     */
282 178 jones
    public QueryGroup(String operator) {
283
      this.operator = operator;
284 158 jones
      children = new Vector();
285
    }
286
287 172 jones
    /**
288
     * Add a child QueryGroup to this QueryGroup
289
     *
290
     * @param qgroup the query group to be added to the list of terms
291
     */
292 158 jones
    public void addChild(QueryGroup qgroup) {
293
      children.add((Object)qgroup);
294
    }
295
296 172 jones
    /**
297
     * Add a child QueryTerm to this QueryGroup
298
     *
299
     * @param qterm the query term to be added to the list of terms
300
     */
301 158 jones
    public void addChild(QueryTerm qterm) {
302
      children.add((Object)qterm);
303
    }
304
305 172 jones
    /**
306
     * Retrieve an Enumeration of query terms for this QueryGroup
307
     */
308 158 jones
    public Enumeration getChildren() {
309
      return children.elements();
310
    }
311 159 jones
312 172 jones
    /**
313
     * create a SQL serialization of the query that this instance represents
314
     */
315
    public String printSQL() {
316 170 jones
      StringBuffer self = new StringBuffer();
317
      boolean first = true;
318
319
      self.append("(");
320
321
      Enumeration en= getChildren();
322
      while (en.hasMoreElements()) {
323
        Object qobject = en.nextElement();
324
        if (first) {
325
          first = false;
326
        } else {
327 178 jones
          self.append(" " + operator + " ");
328 170 jones
        }
329
        if (qobject instanceof QueryGroup) {
330
          QueryGroup qg = (QueryGroup)qobject;
331 172 jones
          self.append(qg.printSQL());
332 170 jones
        } else if (qobject instanceof QueryTerm) {
333
          QueryTerm qt = (QueryTerm)qobject;
334 172 jones
          self.append(qt.printSQL());
335 170 jones
        } else {
336
          System.err.println("qobject wrong type: fatal error");
337
        }
338
      }
339
      self.append(") \n");
340
      return self.toString();
341
    }
342
343 172 jones
    /**
344
     * create a String description of the query that this instance represents.
345
     * This should become a way to get the XML serialization of the query.
346
     */
347 159 jones
    public String toString() {
348
      StringBuffer self = new StringBuffer();
349
350 178 jones
      self.append("  (Query group operator=" + operator + "\n");
351 159 jones
      Enumeration en= getChildren();
352
      while (en.hasMoreElements()) {
353
        Object qobject = en.nextElement();
354
        self.append(qobject);
355
      }
356
      self.append("  )\n");
357
      return self.toString();
358
    }
359 158 jones
  }
360
361
  /** a utility class that represents a single term in a query */
362
  private class QueryTerm {
363
    private boolean casesensitive = false;
364
    private String searchmode = null;
365
    private String value = null;
366
    private String pathexpr = null;
367
368 172 jones
    /**
369
     * Construct a new instance of a query term for a free text search
370
     * (using the value only)
371
     *
372
     * @param casesensitive flag indicating whether case is used to match
373
     * @param searchmode determines what kind of substring match is performed
374
     *        (one of starts-with|ends-with|contains|matches-exactly)
375
     * @param value the text value to match
376
     */
377 158 jones
    public QueryTerm(boolean casesensitive, String searchmode,
378
                     String value) {
379
      this.casesensitive = casesensitive;
380
      this.searchmode = searchmode;
381
      this.value = value;
382
    }
383
384 172 jones
    /**
385
     * Construct a new instance of a query term for a structured search
386
     * (matching the value only for those nodes in the pathexpr)
387
     *
388
     * @param casesensitive flag indicating whether case is used to match
389
     * @param searchmode determines what kind of substring match is performed
390
     *        (one of starts-with|ends-with|contains|matches-exactly)
391
     * @param value the text value to match
392
     * @param pathexpr the hierarchical path to the nodes to be searched
393
     */
394 158 jones
    public QueryTerm(boolean casesensitive, String searchmode,
395
                     String value, String pathexpr) {
396
      this(casesensitive, searchmode, value);
397
      this.pathexpr = pathexpr;
398
    }
399
400 172 jones
    /** determine if the QueryTerm is case sensitive */
401 158 jones
    public boolean isCaseSensitive() {
402
      return casesensitive;
403
    }
404
405 172 jones
    /** get the searchmode parameter */
406 158 jones
    public String getSearchMode() {
407
      return searchmode;
408
    }
409
410 172 jones
    /** get the Value parameter */
411 158 jones
    public String getValue() {
412
      return value;
413
    }
414
415 172 jones
    /** get the path expression parameter */
416 158 jones
    public String getPathExpression() {
417
      return pathexpr;
418
    }
419 159 jones
420 172 jones
    /**
421
     * create a SQL serialization of the query that this instance represents
422
     */
423
    public String printSQL() {
424 170 jones
      StringBuffer self = new StringBuffer();
425
426
      // Uppercase the search string if case match is not important
427
      String casevalue = null;
428
      String nodedataterm = null;
429
430
      if (casesensitive) {
431
        nodedataterm = "nodedata";
432
        casevalue = value;
433
      } else {
434
        nodedataterm = "UPPER(nodedata)";
435
        casevalue = value.toUpperCase();
436
      }
437
438
      // Add appropriate wildcards to search string
439
      String searchvalue = null;
440
      if (searchmode.equals("starts-with")) {
441
        searchvalue = casevalue + "%";
442
      } else if (searchmode.equals("ends-with")) {
443
        searchvalue = "%" + casevalue;
444
      } else if (searchmode.equals("contains")) {
445
        searchvalue = "%" + casevalue + "%";
446
      } else {
447
        searchvalue = casevalue;
448
      }
449
450 178 jones
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
451 170 jones
452
      if (pathexpr != null) {
453
        self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
454
        self.append("AND parentnodeid IN ");
455
        self.append("(SELECT nodeid FROM xml_index WHERE path LIKE " +
456
                    "'" +  pathexpr + "') " );
457
      } else {
458
        self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
459
      }
460
461
      return self.toString();
462
    }
463
464 172 jones
    /**
465
     * create a String description of the query that this instance represents.
466
     * This should become a way to get the XML serialization of the query.
467
     */
468 159 jones
    public String toString() {
469
      StringBuffer self = new StringBuffer();
470
471
      self.append("    Query Term iscasesensitive=" + casesensitive + "\n");
472
      self.append("               searchmode=" + searchmode + "\n");
473
      self.append("               value=" + value + "\n");
474
      if (pathexpr != null) {
475
        self.append("               pathexpr=" + pathexpr + "\n");
476
      }
477
478
      return self.toString();
479
    }
480 158 jones
  }
481 155 jones
}
482 203 jones
483
/**
484
 * '$Log$
485
 * 'Revision 1.9.2.3  2000/06/25 23:38:17  jones
486
 * 'Added RCSfile keyword
487
 * '
488
 * 'Revision 1.9.2.2  2000/06/25 23:34:18  jones
489
 * 'Changed documentation formatting, added log entries at bottom of source files
490
 * ''
491
 */