Project

General

Profile

1
/**
2
 *      Name: QuerySpecification.java
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
 *   Version: '$Id: QuerySpecification.java 185 2000-06-22 02:20:17Z jones $'
12
 */
13

    
14
package edu.ucsb.nceas.metacat;
15

    
16
import java.io.*;
17
import java.util.Stack;
18
import java.util.Vector;
19
import java.util.Enumeration;
20

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

    
29
/** 
30
 * A Class that represents a structured query, and can be 
31
 * constructed from an XML serialization conforming to @see pathquery.dtd. 
32
 * The printSQL() method can be used to print a SQL serialization of the query.
33
 */
34
public class QuerySpecification extends DefaultHandler {
35
 
36
  /** Default parser name. */
37
  private static final String
38
      DEFAULT_PARSER = "org.apache.xerces.parsers.SAXParser";
39
      //DEFAULT_PARSER = "oracle.xml.parser.v2.SAXParser";
40

    
41
  // Query data structures
42
  private String meta_file_id;
43
  private String querytitle;
44
  private Vector doctypeList;
45
  private QueryGroup query = null;
46

    
47
  private Stack elementStack;
48
  private Stack queryStack;
49
  private String currentValue;
50
  private String currentPathexpr;
51
  private String parserName = null;
52

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

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

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

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

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

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

    
112
         // Also test the XML formatting function of DBQuery
113
/*
114
         System.out.println(DBQuery.createQuery("mysearchvalue", "mydoctype"));
115
         StringReader xml2 = new StringReader(
116
                      DBQuery.createQuery("mysearchvalue", "mydoctype"));
117
         QuerySpecification qspec2 = 
118
                      new QuerySpecification(xml2, DEFAULT_PARSER);
119
         System.out.println(qspec2.printSQL());
120
*/
121
       } catch (IOException e) {
122
         System.err.println(e.getMessage());
123
       }
124
         
125
     }
126
  }
127

    
128
  /**
129
   * Set up the SAX parser for reading the XML serialized query
130
   */
131
  private XMLReader initializeParser() {
132
    XMLReader parser = null;
133

    
134
    // Set up the SAX document handlers for parsing
135
    try {
136

    
137
      // Get an instance of the parser
138
      parser = XMLReaderFactory.createXMLReader(parserName);
139

    
140
      // Set the ContentHandler to this instance
141
      parser.setContentHandler(this);
142

    
143
      // Set the error Handler to this instance
144
      parser.setErrorHandler(this);
145

    
146
    } catch (Exception e) {
147
       System.err.println(e.toString());
148
    }
149

    
150
    return parser;
151
  }
152

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

    
170
    elementStack.push(currentNode); 
171
    if (currentNode.getTagName().equals("querygroup")) {
172
      QueryGroup currentGroup = new QueryGroup(
173
                                currentNode.getAttribute("operator"));
174
      if (query == null) {
175
        query = currentGroup;
176
      } else {
177
        QueryGroup parentGroup = (QueryGroup)queryStack.peek();
178
        parentGroup.addChild(currentGroup);
179
      }
180
      queryStack.push(currentGroup);
181
    }
182
  }
183

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

    
213
  /**
214
   * callback method used by the SAX Parser when the text sequences of an 
215
   * xml stream are detected. Used in this context to parse and store
216
   * the query information in class variables.
217
   */
218
  public void characters(char ch[], int start, int length) {
219

    
220
    String inputString = new String(ch, start, length);
221
    BasicNode currentNode = (BasicNode)elementStack.peek(); 
222
    String currentTag = currentNode.getTagName();
223
    if (currentTag.equals("meta_file_id")) {
224
      meta_file_id = inputString;
225
    } else if (currentTag.equals("querytitle")) {
226
      querytitle = inputString;
227
    } else if (currentTag.equals("value")) {
228
      currentValue = inputString;
229
    } else if (currentTag.equals("pathexpr")) {
230
      currentPathexpr = inputString;
231
    } else if (currentTag.equals("returndoctype")) {
232
      doctypeList.add(inputString);
233
    }
234
  }
235

    
236

    
237
  /**
238
   * create a SQL serialization of the query that this instance represents
239
   */
240
  public String printSQL() {
241
    StringBuffer self = new StringBuffer();
242

    
243
    // This determines the returned results
244
    self.append("SELECT docid,docname,doctype,doctitle ");
245
    self.append("FROM xml_documents WHERE docid IN (");
246

    
247
    // This determines the documents that meet the query conditions
248
    self.append(query.printSQL());
249

    
250
    self.append(") ");
251

    
252
    // Add SQL to filter for doctypes requested in the query
253
    if (!doctypeList.isEmpty()) {
254
      boolean firstdoctype = true;
255
      self.append(" AND ("); 
256
      Enumeration en = doctypeList.elements();
257
      while (en.hasMoreElements()) {
258
        String currentDoctype = (String)en.nextElement();
259
        if (firstdoctype) {
260
          firstdoctype = false;
261
          self.append(" doctype = '" + currentDoctype + "'"); 
262
        } else {
263
          self.append(" OR doctype = '" + currentDoctype + "'"); 
264
        }
265
      }
266
      self.append(") ");
267
    }
268

    
269
    return self.toString();
270
  }
271

    
272
  /**
273
   * create a String description of the query that this instance represents.
274
   * This should become a way to get the XML serialization of the query.
275
   */
276
  public String toString() {
277
    return "meta_file_id=" + meta_file_id + "\n" + 
278
           "querytitle=" + querytitle + "\n" + query;
279
  }
280

    
281
  /** a utility class that represents a group of terms in a query */
282
  private class QueryGroup {
283
    private String operator = null;  // indicates how query terms are combined
284
    private Vector children = null;  // the list of query terms and groups
285

    
286
    /** 
287
     * construct a new QueryGroup 
288
     *
289
     * @param operator the boolean conector used to connect query terms 
290
     *                    in this query group
291
     */
292
    public QueryGroup(String operator) {
293
      this.operator = operator;
294
      children = new Vector();
295
    }
296

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

    
306
    /**
307
     * Add a child QueryTerm to this QueryGroup
308
     *
309
     * @param qterm the query term to be added to the list of terms
310
     */
311
    public void addChild(QueryTerm qterm) {
312
      children.add((Object)qterm); 
313
    }
314

    
315
    /**
316
     * Retrieve an Enumeration of query terms for this QueryGroup
317
     */
318
    public Enumeration getChildren() {
319
      return children.elements();
320
    }
321
   
322
    /**
323
     * create a SQL serialization of the query that this instance represents
324
     */
325
    public String printSQL() {
326
      StringBuffer self = new StringBuffer();
327
      boolean first = true;
328

    
329
      self.append("(");
330

    
331
      Enumeration en= getChildren();
332
      while (en.hasMoreElements()) {
333
        Object qobject = en.nextElement();
334
        if (first) {
335
          first = false;
336
        } else {
337
          self.append(" " + operator + " ");
338
        }
339
        if (qobject instanceof QueryGroup) {
340
          QueryGroup qg = (QueryGroup)qobject;
341
          self.append(qg.printSQL());
342
        } else if (qobject instanceof QueryTerm) {
343
          QueryTerm qt = (QueryTerm)qobject;
344
          self.append(qt.printSQL());
345
        } else {
346
          System.err.println("qobject wrong type: fatal error");
347
        }
348
      }
349
      self.append(") \n");
350
      return self.toString();
351
    }
352

    
353
    /**
354
     * create a String description of the query that this instance represents.
355
     * This should become a way to get the XML serialization of the query.
356
     */
357
    public String toString() {
358
      StringBuffer self = new StringBuffer();
359

    
360
      self.append("  (Query group operator=" + operator + "\n");
361
      Enumeration en= getChildren();
362
      while (en.hasMoreElements()) {
363
        Object qobject = en.nextElement();
364
        self.append(qobject);
365
      }
366
      self.append("  )\n");
367
      return self.toString();
368
    }
369
  }
370

    
371
  /** a utility class that represents a single term in a query */
372
  private class QueryTerm {
373
    private boolean casesensitive = false;
374
    private String searchmode = null;
375
    private String value = null;
376
    private String pathexpr = null;
377

    
378
    /**
379
     * Construct a new instance of a query term for a free text search
380
     * (using the value only)
381
     *
382
     * @param casesensitive flag indicating whether case is used to match
383
     * @param searchmode determines what kind of substring match is performed
384
     *        (one of starts-with|ends-with|contains|matches-exactly)
385
     * @param value the text value to match
386
     */
387
    public QueryTerm(boolean casesensitive, String searchmode, 
388
                     String value) {
389
      this.casesensitive = casesensitive;
390
      this.searchmode = searchmode;
391
      this.value = value;
392
    }
393

    
394
    /**
395
     * Construct a new instance of a query term for a structured search
396
     * (matching the value only for those nodes in the pathexpr)
397
     *
398
     * @param casesensitive flag indicating whether case is used to match
399
     * @param searchmode determines what kind of substring match is performed
400
     *        (one of starts-with|ends-with|contains|matches-exactly)
401
     * @param value the text value to match
402
     * @param pathexpr the hierarchical path to the nodes to be searched
403
     */
404
    public QueryTerm(boolean casesensitive, String searchmode, 
405
                     String value, String pathexpr) {
406
      this(casesensitive, searchmode, value);
407
      this.pathexpr = pathexpr;
408
    }
409

    
410
    /** determine if the QueryTerm is case sensitive */
411
    public boolean isCaseSensitive() {
412
      return casesensitive;
413
    }
414

    
415
    /** get the searchmode parameter */
416
    public String getSearchMode() {
417
      return searchmode;
418
    }
419
 
420
    /** get the Value parameter */
421
    public String getValue() {
422
      return value;
423
    }
424

    
425
    /** get the path expression parameter */
426
    public String getPathExpression() {
427
      return pathexpr;
428
    }
429

    
430
    /**
431
     * create a SQL serialization of the query that this instance represents
432
     */
433
    public String printSQL() {
434
      StringBuffer self = new StringBuffer();
435

    
436
      // Uppercase the search string if case match is not important
437
      String casevalue = null;
438
      String nodedataterm = null;
439

    
440
      if (casesensitive) {
441
        nodedataterm = "nodedata";
442
        casevalue = value;
443
      } else {
444
        nodedataterm = "UPPER(nodedata)";
445
        casevalue = value.toUpperCase();
446
      }
447

    
448
      // Add appropriate wildcards to search string
449
      String searchvalue = null;
450
      if (searchmode.equals("starts-with")) {
451
        searchvalue = casevalue + "%";
452
      } else if (searchmode.equals("ends-with")) {
453
        searchvalue = "%" + casevalue;
454
      } else if (searchmode.equals("contains")) {
455
        searchvalue = "%" + casevalue + "%";
456
      } else {
457
        searchvalue = casevalue;
458
      }
459

    
460
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
461

    
462
      if (pathexpr != null) {
463
        self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
464
        self.append("AND parentnodeid IN ");
465
        self.append("(SELECT nodeid FROM xml_index WHERE path LIKE " + 
466
                    "'" +  pathexpr + "') " );
467
      } else {
468
        self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
469
      }
470

    
471
      return self.toString();
472
    }
473

    
474
    /**
475
     * create a String description of the query that this instance represents.
476
     * This should become a way to get the XML serialization of the query.
477
     */
478
    public String toString() {
479
      StringBuffer self = new StringBuffer();
480

    
481
      self.append("    Query Term iscasesensitive=" + casesensitive + "\n");
482
      self.append("               searchmode=" + searchmode + "\n");
483
      self.append("               value=" + value + "\n");
484
      if (pathexpr != null) {
485
        self.append("               pathexpr=" + pathexpr + "\n");
486
      }
487

    
488
      return self.toString();
489
    }
490
  }
491
}
(17-17/18)