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 349 jones
 *    Release: @release@
11 155 jones
 *
12 203 jones
 *   '$Author$'
13
 *     '$Date$'
14
 * '$Revision$'
15 155 jones
 */
16
17
package edu.ucsb.nceas.metacat;
18
19
import java.io.*;
20
import java.util.Stack;
21 158 jones
import java.util.Vector;
22 159 jones
import java.util.Enumeration;
23 155 jones
24 185 jones
import org.xml.sax.Attributes;
25 158 jones
import org.xml.sax.InputSource;
26
import org.xml.sax.SAXException;
27
import org.xml.sax.SAXParseException;
28 185 jones
import org.xml.sax.XMLReader;
29
import org.xml.sax.helpers.XMLReaderFactory;
30
import org.xml.sax.helpers.DefaultHandler;
31 155 jones
32
/**
33 172 jones
 * 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 155 jones
 */
37 185 jones
public class QuerySpecification extends DefaultHandler {
38 155 jones
39 158 jones
  // Query data structures
40
  private String meta_file_id;
41
  private String querytitle;
42 172 jones
  private Vector doctypeList;
43 158 jones
  private QueryGroup query = null;
44
45
  private Stack elementStack;
46
  private Stack queryStack;
47 159 jones
  private String currentValue;
48
  private String currentPathexpr;
49 172 jones
  private String parserName = null;
50 158 jones
51 155 jones
  /**
52
   * construct an instance of the QuerySpecification class
53
   *
54 172 jones
   * @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 185 jones
   *                  the org.xml.sax.XMLReader interface
58 155 jones
   */
59 172 jones
  public QuerySpecification( Reader queryspec, String parserName )
60
         throws IOException {
61 155 jones
    super();
62 158 jones
63 172 jones
    // Initialize the class variables
64
    doctypeList = new Vector();
65 158 jones
    elementStack = new Stack();
66
    queryStack   = new Stack();
67 172 jones
    this.parserName = parserName;
68 158 jones
69
    // Initialize the parser and read the queryspec
70 185 jones
    XMLReader parser = initializeParser();
71 181 jones
    if (parser == null) {
72
      System.err.println("SAX parser not instantiated properly.");
73
    }
74 155 jones
    try {
75
      parser.parse(new InputSource(queryspec));
76
    } catch (SAXException e) {
77 180 jones
      System.err.println("error parsing data");
78
      System.err.println(e.getMessage());
79 155 jones
    }
80
  }
81
82
  /**
83
   * construct an instance of the QuerySpecification class
84
   *
85 172 jones
   * @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 155 jones
   */
90 172 jones
  public QuerySpecification( String queryspec, String parserName )
91
         throws IOException {
92
    this(new StringReader(queryspec), parserName);
93 155 jones
  }
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 203 jones
         MetaCatUtil util = new MetaCatUtil();
107 155 jones
         FileReader xml = new FileReader(new File(xmlfile));
108 203 jones
         QuerySpecification qspec =
109
                 new QuerySpecification(xml, util.getOption("saxparser"));
110 172 jones
         System.out.println(qspec.printSQL());
111 181 jones
112 155 jones
       } catch (IOException e) {
113
         System.err.println(e.getMessage());
114
       }
115
116
     }
117
  }
118
119 172 jones
  /**
120
   * Set up the SAX parser for reading the XML serialized query
121
   */
122 185 jones
  private XMLReader initializeParser() {
123
    XMLReader parser = null;
124 172 jones
125 155 jones
    // Set up the SAX document handlers for parsing
126
    try {
127
128
      // Get an instance of the parser
129 185 jones
      parser = XMLReaderFactory.createXMLReader(parserName);
130 155 jones
131 185 jones
      // Set the ContentHandler to this instance
132
      parser.setContentHandler(this);
133 155 jones
134 185 jones
      // Set the error Handler to this instance
135 158 jones
      parser.setErrorHandler(this);
136 155 jones
137
    } catch (Exception e) {
138
       System.err.println(e.toString());
139
    }
140
141
    return parser;
142
  }
143
144 172 jones
  /**
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 185 jones
  public void startElement (String uri, String localName,
150
                            String qName, Attributes atts)
151 155 jones
         throws SAXException {
152 185 jones
    BasicNode currentNode = new BasicNode(localName);
153 159 jones
    // add attributes to BasicNode here
154
    if (atts != null) {
155
      int len = atts.getLength();
156
      for (int i = 0; i < len; i++) {
157 185 jones
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
158 159 jones
      }
159
    }
160
161 158 jones
    elementStack.push(currentNode);
162 159 jones
    if (currentNode.getTagName().equals("querygroup")) {
163 158 jones
      QueryGroup currentGroup = new QueryGroup(
164 178 jones
                                currentNode.getAttribute("operator"));
165 158 jones
      if (query == null) {
166
        query = currentGroup;
167 159 jones
      } else {
168
        QueryGroup parentGroup = (QueryGroup)queryStack.peek();
169
        parentGroup.addChild(currentGroup);
170 158 jones
      }
171 159 jones
      queryStack.push(currentGroup);
172 158 jones
    }
173 155 jones
  }
174
175 172 jones
  /**
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 185 jones
  public void endElement (String uri, String localName,
181
                          String qName) throws SAXException {
182 158 jones
    BasicNode leaving = (BasicNode)elementStack.pop();
183 159 jones
    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 158 jones
    }
202 155 jones
  }
203 158 jones
204 172 jones
  /**
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 158 jones
  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 159 jones
    } else if (currentTag.equals("value")) {
219
      currentValue = inputString;
220
    } else if (currentTag.equals("pathexpr")) {
221
      currentPathexpr = inputString;
222 172 jones
    } else if (currentTag.equals("returndoctype")) {
223
      doctypeList.add(inputString);
224 158 jones
    }
225
  }
226
227 172 jones
228
  /**
229
   * create a SQL serialization of the query that this instance represents
230
   */
231
  public String printSQL() {
232 170 jones
    StringBuffer self = new StringBuffer();
233
234 172 jones
    // This determines the returned results
235
    self.append("SELECT docid,docname,doctype,doctitle ");
236
    self.append("FROM xml_documents WHERE docid IN (");
237 170 jones
238 178 jones
    // This determines the documents that meet the query conditions
239 172 jones
    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 170 jones
    return self.toString();
261
  }
262
263 172 jones
  /**
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 159 jones
  public String toString() {
268
    return "meta_file_id=" + meta_file_id + "\n" +
269
           "querytitle=" + querytitle + "\n" + query;
270
  }
271
272 158 jones
  /** a utility class that represents a group of terms in a query */
273
  private class QueryGroup {
274 178 jones
    private String operator = null;  // indicates how query terms are combined
275
    private Vector children = null;  // the list of query terms and groups
276 158 jones
277 172 jones
    /**
278
     * construct a new QueryGroup
279
     *
280 178 jones
     * @param operator the boolean conector used to connect query terms
281 172 jones
     *                    in this query group
282
     */
283 178 jones
    public QueryGroup(String operator) {
284
      this.operator = operator;
285 158 jones
      children = new Vector();
286
    }
287
288 172 jones
    /**
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 158 jones
    public void addChild(QueryGroup qgroup) {
294
      children.add((Object)qgroup);
295
    }
296
297 172 jones
    /**
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 158 jones
    public void addChild(QueryTerm qterm) {
303
      children.add((Object)qterm);
304
    }
305
306 172 jones
    /**
307
     * Retrieve an Enumeration of query terms for this QueryGroup
308
     */
309 158 jones
    public Enumeration getChildren() {
310
      return children.elements();
311
    }
312 159 jones
313 172 jones
    /**
314
     * create a SQL serialization of the query that this instance represents
315
     */
316
    public String printSQL() {
317 170 jones
      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 178 jones
          self.append(" " + operator + " ");
329 170 jones
        }
330
        if (qobject instanceof QueryGroup) {
331
          QueryGroup qg = (QueryGroup)qobject;
332 172 jones
          self.append(qg.printSQL());
333 170 jones
        } else if (qobject instanceof QueryTerm) {
334
          QueryTerm qt = (QueryTerm)qobject;
335 172 jones
          self.append(qt.printSQL());
336 170 jones
        } else {
337
          System.err.println("qobject wrong type: fatal error");
338
        }
339
      }
340
      self.append(") \n");
341
      return self.toString();
342
    }
343
344 172 jones
    /**
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 159 jones
    public String toString() {
349
      StringBuffer self = new StringBuffer();
350
351 178 jones
      self.append("  (Query group operator=" + operator + "\n");
352 159 jones
      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 158 jones
  }
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 172 jones
    /**
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 158 jones
    public QueryTerm(boolean casesensitive, String searchmode,
379
                     String value) {
380
      this.casesensitive = casesensitive;
381
      this.searchmode = searchmode;
382
      this.value = value;
383
    }
384
385 172 jones
    /**
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 158 jones
    public QueryTerm(boolean casesensitive, String searchmode,
396
                     String value, String pathexpr) {
397
      this(casesensitive, searchmode, value);
398
      this.pathexpr = pathexpr;
399
    }
400
401 172 jones
    /** determine if the QueryTerm is case sensitive */
402 158 jones
    public boolean isCaseSensitive() {
403
      return casesensitive;
404
    }
405
406 172 jones
    /** get the searchmode parameter */
407 158 jones
    public String getSearchMode() {
408
      return searchmode;
409
    }
410
411 172 jones
    /** get the Value parameter */
412 158 jones
    public String getValue() {
413
      return value;
414
    }
415
416 172 jones
    /** get the path expression parameter */
417 158 jones
    public String getPathExpression() {
418
      return pathexpr;
419
    }
420 159 jones
421 172 jones
    /**
422
     * create a SQL serialization of the query that this instance represents
423
     */
424
    public String printSQL() {
425 170 jones
      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 178 jones
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
452 170 jones
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 172 jones
    /**
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 159 jones
    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 158 jones
  }
482 155 jones
}
483 203 jones
484
/**
485
 * '$Log$
486 349 jones
 * '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 203 jones
 * '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
 */