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 172 2000-06-16 23:24:46Z 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.AttributeList;
22
import org.xml.sax.InputSource;
23
import org.xml.sax.HandlerBase;
24
import org.xml.sax.Parser;
25
import org.xml.sax.SAXException;
26
import org.xml.sax.SAXParseException;
27
import org.xml.sax.helpers.ParserFactory;
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 HandlerBase {
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.Parser 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
    Parser parser = initializeParser();
73
    try {
74
      parser.parse(new InputSource(queryspec));
75
    } catch (SAXException e) {
76
      System.out.println("error parsing data");
77
      System.out.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
         FileReader xml = new FileReader(new File(xmlfile));
106
         QuerySpecification qspec = new QuerySpecification(xml, DEFAULT_PARSER);
107
         System.out.println(qspec.printSQL());
108
       } catch (IOException e) {
109
         System.err.println(e.getMessage());
110
       }
111
         
112
     }
113
  }
114

    
115
  /**
116
   * Set up the SAX parser for reading the XML serialized query
117
   */
118
  private Parser initializeParser() {
119
    Parser parser = null;
120

    
121
    // Set up the SAX document handlers for parsing
122
    try {
123

    
124
      // Get an instance of the parser
125
      parser = ParserFactory.makeParser(parserName);
126

    
127
      // Set the DocumentHandler to this instance
128
      parser.setDocumentHandler(this);
129

    
130
      // Set the other Handler to the defHandler
131
      parser.setErrorHandler(this);
132

    
133
    } catch (Exception e) {
134
       System.err.println(e.toString());
135
    }
136

    
137
    return parser;
138
  }
139

    
140
  /**
141
   * callback method used by the SAX Parser when the start tag of an 
142
   * element is detected. Used in this context to parse and store
143
   * the query information in class variables.
144
   */
145
  public void startElement (String name, AttributeList atts) 
146
         throws SAXException {
147

    
148
    BasicNode currentNode = new BasicNode(name);
149
    // add attributes to BasicNode here
150
    if (atts != null) {
151
      int len = atts.getLength();
152
      for (int i = 0; i < len; i++) {
153
        currentNode.setAttribute(atts.getName(i), atts.getValue(i));
154
      }
155
    }
156

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

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

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

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

    
222

    
223
  /**
224
   * create a SQL serialization of the query that this instance represents
225
   */
226
  public String printSQL() {
227
    StringBuffer self = new StringBuffer();
228

    
229
    // This determines the returned results
230
    self.append("SELECT docid,docname,doctype,doctitle ");
231
    self.append("FROM xml_documents WHERE docid IN (");
232
    self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
233

    
234
    // This determines the WHERE conditions
235
    self.append(query.printSQL());
236

    
237
    self.append(") ");
238

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

    
256
    return self.toString();
257
  }
258

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

    
268
  /** a utility class that represents a group of terms in a query */
269
  private class QueryGroup {
270
    private String booleantype = null; // indicates how query terms are combined
271
    private Vector children = null;    // the list of query terms and groups
272

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

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

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

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

    
316
      self.append("(");
317

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

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

    
347
      self.append("  (Query group booleantype=" + booleantype + "\n");
348
      Enumeration en= getChildren();
349
      while (en.hasMoreElements()) {
350
        Object qobject = en.nextElement();
351
        self.append(qobject);
352
      }
353
      self.append("  )\n");
354
      return self.toString();
355
    }
356
  }
357

    
358
  /** a utility class that represents a single term in a query */
359
  private class QueryTerm {
360
    private boolean casesensitive = false;
361
    private String searchmode = null;
362
    private String value = null;
363
    private String pathexpr = null;
364

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

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

    
397
    /** determine if the QueryTerm is case sensitive */
398
    public boolean isCaseSensitive() {
399
      return casesensitive;
400
    }
401

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

    
412
    /** get the path expression parameter */
413
    public String getPathExpression() {
414
      return pathexpr;
415
    }
416

    
417
    /**
418
     * create a SQL serialization of the query that this instance represents
419
     */
420
    public String printSQL() {
421
      StringBuffer self = new StringBuffer();
422

    
423
      // Uppercase the search string if case match is not important
424
      String casevalue = null;
425
      String nodedataterm = null;
426

    
427
      if (casesensitive) {
428
        nodedataterm = "nodedata";
429
        casevalue = value;
430
      } else {
431
        nodedataterm = "UPPER(nodedata)";
432
        casevalue = value.toUpperCase();
433
      }
434

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

    
447
      self.append("(");
448

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

    
458
      self.append(") \n");
459

    
460
      return self.toString();
461
    }
462

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

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

    
477
      return self.toString();
478
    }
479
  }
480
}
(18-18/20)