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 180 2000-06-20 22:45:22Z 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.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
         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("operator"));
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

    
233
    // This determines the documents that meet the query conditions
234
    self.append(query.printSQL());
235

    
236
    self.append(") ");
237

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

    
255
    return self.toString();
256
  }
257

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

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

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

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

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

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

    
315
      self.append("(");
316

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

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

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

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

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

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

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

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

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

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

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

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

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

    
446
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
447

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

    
457
      return self.toString();
458
    }
459

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

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

    
474
      return self.toString();
475
    }
476
  }
477
}
(18-18/20)