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
 *
11
 *   '$Author: jones $'
12
 *     '$Date: 2000-06-26 03:35:05 -0700 (Mon, 26 Jun 2000) $'
13
 * '$Revision: 203 $'
14
 */
15

    
16
package edu.ucsb.nceas.metacat;
17

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

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

    
31
/** 
32
 * 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
 */
36
public class QuerySpecification extends DefaultHandler {
37
 
38
  // Query data structures
39
  private String meta_file_id;
40
  private String querytitle;
41
  private Vector doctypeList;
42
  private QueryGroup query = null;
43

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

    
50
  /**
51
   * construct an instance of the QuerySpecification class 
52
   *
53
   * @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
   *                  the org.xml.sax.XMLReader interface
57
   */
58
  public QuerySpecification( Reader queryspec, String parserName ) 
59
         throws IOException {
60
    super();
61

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

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

    
81
  /**
82
   * construct an instance of the QuerySpecification class 
83
   *
84
   * @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
   */
89
  public QuerySpecification( String queryspec, String parserName ) 
90
         throws IOException {
91
    this(new StringReader(queryspec), parserName);
92
  }
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
         MetaCatUtil util = new MetaCatUtil();
106
         FileReader xml = new FileReader(new File(xmlfile));
107
         QuerySpecification qspec = 
108
                 new QuerySpecification(xml, util.getOption("saxparser"));
109
         System.out.println(qspec.printSQL());
110

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

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

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

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

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

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

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

    
140
    return parser;
141
  }
142

    
143
  /**
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
  public void startElement (String uri, String localName, 
149
                            String qName, Attributes atts) 
150
         throws SAXException {
151
    BasicNode currentNode = new BasicNode(localName);
152
    // add attributes to BasicNode here
153
    if (atts != null) {
154
      int len = atts.getLength();
155
      for (int i = 0; i < len; i++) {
156
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
157
      }
158
    }
159

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

    
174
  /**
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
  public void endElement (String uri, String localName,
180
                          String qName) throws SAXException {
181
    BasicNode leaving = (BasicNode)elementStack.pop(); 
182
    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
    }
201
  }
202

    
203
  /**
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
  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
    } else if (currentTag.equals("value")) {
218
      currentValue = inputString;
219
    } else if (currentTag.equals("pathexpr")) {
220
      currentPathexpr = inputString;
221
    } else if (currentTag.equals("returndoctype")) {
222
      doctypeList.add(inputString);
223
    }
224
  }
225

    
226

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

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

    
237
    // This determines the documents that meet the query conditions
238
    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
    return self.toString();
260
  }
261

    
262
  /**
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
  public String toString() {
267
    return "meta_file_id=" + meta_file_id + "\n" + 
268
           "querytitle=" + querytitle + "\n" + query;
269
  }
270

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

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

    
287
    /** 
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
    public void addChild(QueryGroup qgroup) {
293
      children.add((Object)qgroup); 
294
    }
295

    
296
    /**
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
    public void addChild(QueryTerm qterm) {
302
      children.add((Object)qterm); 
303
    }
304

    
305
    /**
306
     * Retrieve an Enumeration of query terms for this QueryGroup
307
     */
308
    public Enumeration getChildren() {
309
      return children.elements();
310
    }
311
   
312
    /**
313
     * create a SQL serialization of the query that this instance represents
314
     */
315
    public String printSQL() {
316
      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
          self.append(" " + operator + " ");
328
        }
329
        if (qobject instanceof QueryGroup) {
330
          QueryGroup qg = (QueryGroup)qobject;
331
          self.append(qg.printSQL());
332
        } else if (qobject instanceof QueryTerm) {
333
          QueryTerm qt = (QueryTerm)qobject;
334
          self.append(qt.printSQL());
335
        } else {
336
          System.err.println("qobject wrong type: fatal error");
337
        }
338
      }
339
      self.append(") \n");
340
      return self.toString();
341
    }
342

    
343
    /**
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
    public String toString() {
348
      StringBuffer self = new StringBuffer();
349

    
350
      self.append("  (Query group operator=" + operator + "\n");
351
      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
  }
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
    /**
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
    public QueryTerm(boolean casesensitive, String searchmode, 
378
                     String value) {
379
      this.casesensitive = casesensitive;
380
      this.searchmode = searchmode;
381
      this.value = value;
382
    }
383

    
384
    /**
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
    public QueryTerm(boolean casesensitive, String searchmode, 
395
                     String value, String pathexpr) {
396
      this(casesensitive, searchmode, value);
397
      this.pathexpr = pathexpr;
398
    }
399

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

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

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

    
420
    /**
421
     * create a SQL serialization of the query that this instance represents
422
     */
423
    public String printSQL() {
424
      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
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
451

    
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
    /**
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
    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
  }
481
}
482

    
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
 */
(22-22/24)