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
 *    Release: @release@
11
 *
12
 *   '$Author: bojilova $'
13
 *     '$Date: 2001-07-17 09:20:02 -0700 (Tue, 17 Jul 2001) $'
14
 * '$Revision: 795 $'
15
 *
16
 * This program is free software; you can redistribute it and/or modify
17
 * it under the terms of the GNU General Public License as published by
18
 * the Free Software Foundation; either version 2 of the License, or
19
 * (at your option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 * GNU General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU General Public License
27
 * along with this program; if not, write to the Free Software
28
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
29
 */
30

    
31
package edu.ucsb.nceas.metacat;
32

    
33
import java.io.*;
34
import java.util.Stack;
35
import java.util.Vector;
36
import java.util.Enumeration;
37

    
38
import org.xml.sax.Attributes;
39
import org.xml.sax.InputSource;
40
import org.xml.sax.SAXException;
41
import org.xml.sax.SAXParseException;
42
import org.xml.sax.XMLReader;
43
import org.xml.sax.helpers.XMLReaderFactory;
44
import org.xml.sax.helpers.DefaultHandler;
45

    
46
/**
47
 * A Class that represents a structured query, and can be 
48
 * constructed from an XML serialization conforming to @see pathquery.dtd. 
49
 * The printSQL() method can be used to print a SQL serialization of the query.
50
 */
51
public class QuerySpecification extends DefaultHandler {
52
 
53
  /** flag determining whether extended query terms are present */
54
  private boolean containsExtendedSQL=false;
55
  /** Identifier for this query document */
56
  private String meta_file_id;
57
  /** Title of this query */
58
  private String queryTitle;
59
  /** List of document types to be returned using package back tracing */
60
  private Vector returnDocList;
61
  /** List of document types to be searched */
62
  private Vector filterDocList;
63
  /** List of fields to be returned in result set */
64
  private Vector returnFieldList;
65
  /** List of users owning documents to be searched */
66
  private Vector ownerList;
67
  /** List of sites/scopes used to constrain search */
68
  private Vector siteList;
69
  /** The root query group that contains the recursive query constraints */
70
  private QueryGroup query = null;
71

    
72
  // Query data structures used temporarily during XML parsing
73
  private Stack elementStack;
74
  private Stack queryStack;
75
  private String currentValue;
76
  private String currentPathexpr;
77
  private String parserName = null;
78
  private String accNumberSeparator = null;
79

    
80
  /**
81
   * construct an instance of the QuerySpecification class 
82
   *
83
   * @param queryspec the XML representation of the query (should conform
84
   *                  to pathquery.dtd) as a Reader
85
   * @param parserName the fully qualified name of a Java Class implementing
86
   *                  the org.xml.sax.XMLReader interface
87
   */
88
  public QuerySpecification( Reader queryspec, String parserName,
89
         String accNumberSeparator ) throws IOException {
90
    super();
91
    
92
    // Initialize the class variables
93
    returnDocList = new Vector();
94
    filterDocList = new Vector();
95
    elementStack = new Stack();
96
    queryStack   = new Stack();
97
    returnFieldList = new Vector();
98
    ownerList = new Vector();
99
    siteList = new Vector();
100
    this.parserName = parserName;
101
    this.accNumberSeparator = accNumberSeparator;
102

    
103
    // Initialize the parser and read the queryspec
104
    XMLReader parser = initializeParser();
105
    if (parser == null) {
106
      System.err.println("SAX parser not instantiated properly.");
107
    }
108
    try {
109
      parser.parse(new InputSource(queryspec));
110
    } catch (SAXException e) {
111
      System.err.println("error parsing data in " + 
112
                         "QuerySpecification.QuerySpecification");
113
      System.err.println(e.getMessage());
114
    }
115
  }
116

    
117
  /**
118
   * construct an instance of the QuerySpecification class 
119
   *
120
   * @param queryspec the XML representation of the query (should conform
121
   *                  to pathquery.dtd) as a String
122
   * @param parserName the fully qualified name of a Java Class implementing
123
   *                  the org.xml.sax.Parser interface
124
   */
125
  public QuerySpecification( String queryspec, String parserName,
126
         String accNumberSeparator) throws IOException {
127
    this(new StringReader(queryspec), parserName, accNumberSeparator);
128
  }
129

    
130
  /** Main routine for testing */
131
  static public void main(String[] args) {
132

    
133
     if (args.length < 1) {
134
       System.err.println("Wrong number of arguments!!!");
135
       System.err.println("USAGE: java QuerySpecification <xmlfile>");
136
       return;
137
     } else {
138
       int i = 0;
139
       boolean useXMLIndex = true;
140
       if ( args[i].equals( "-noindex" ) ) {
141
         useXMLIndex = false;
142
         i++;
143
       }
144
       String xmlfile  = args[i];
145

    
146
       try {
147
         MetaCatUtil util = new MetaCatUtil();
148
         FileReader xml = new FileReader(new File(xmlfile));
149
         QuerySpecification qspec = 
150
                 new QuerySpecification(xml, util.getOption("saxparser"),
151
                                        util.getOption("accNumberSeparator"));
152
         System.out.println(qspec.printSQL(useXMLIndex));
153

    
154
       } catch (IOException e) {
155
         System.err.println(e.getMessage());
156
       }
157
         
158
     }
159
  }
160
  
161
  /**
162
   * Returns true if the parsed query contains and extended xml query 
163
   * (i.e. there is at least one &lt;returnfield&gt; in the pathquery document)
164
   */
165
  public boolean containsExtendedSQL()
166
  {
167
    if(containsExtendedSQL)
168
    {
169
      return true;
170
    }
171
    else
172
    {
173
      return false;
174
    }
175
  }
176
  
177
  /**
178
   * Accessor method to return the identifier of this Query
179
   */
180
  public String getIdentifier()
181
  {
182
    return meta_file_id;
183
  }
184

    
185
  /**
186
   * method to set the identifier of this query
187
   */
188
  public void setIdentifier(String id) {
189
    this.meta_file_id = id;
190
  }
191

    
192
  /**
193
   * Accessor method to return the title of this Query
194
   */
195
  public String getQueryTitle()
196
  {
197
    return queryTitle;
198
  }
199

    
200
  /**
201
   * method to set the title of this query
202
   */
203
  public void setQueryTitle(String title)
204
  {
205
    this.queryTitle = title;
206
  }
207

    
208
  /**
209
   * Accessor method to return a vector of the return document types as
210
   * defined in the &lt;returndoctype&gt; tag in the pathquery dtd.
211
   */
212
  public Vector getReturnDocList()
213
  {
214
    return this.returnDocList;
215
  }
216

    
217
  /**
218
   * method to set the list of return docs of this query
219
   */
220
  public void setReturnDocList(Vector returnDocList)
221
  {
222
    this.returnDocList = returnDocList;
223
  }
224

    
225
  /**
226
   * Accessor method to return a vector of the filter doc types as
227
   * defined in the &lt;filterdoctype&gt; tag in the pathquery dtd.
228
   */
229
  public Vector getFilterDocList()
230
  {
231
    return this.filterDocList;
232
  }
233

    
234
  /**
235
   * method to set the list of filter docs of this query
236
   */
237
  public void setFilterDocList(Vector filterDocList)
238
  {
239
    this.filterDocList = filterDocList;
240
  }
241

    
242
  /**
243
   * Accessor method to return a vector of the extended return fields as
244
   * defined in the &lt;returnfield&gt; tag in the pathquery dtd.
245
   */
246
  public Vector getReturnFieldList()
247
  {
248
    return this.returnFieldList; 
249
  }
250

    
251
  /**
252
   * method to set the list of fields to be returned by this query
253
   */
254
  public void setReturnFieldList(Vector returnFieldList)
255
  {
256
    this.returnFieldList = returnFieldList;
257
  }
258

    
259
  /**
260
   * Accessor method to return a vector of the owner fields as
261
   * defined in the &lt;owner&gt; tag in the pathquery dtd.
262
   */
263
  public Vector getOwnerList()
264
  {
265
    return this.ownerList;
266
  }
267

    
268
  /**
269
   * method to set the list of owners used to constrain this query
270
   */
271
  public void setOwnerList(Vector ownerList)
272
  {
273
    this.ownerList = ownerList;
274
  }
275

    
276
  /**
277
   * Accessor method to return a vector of the site fields as
278
   * defined in the &lt;site&gt; tag in the pathquery dtd.
279
   */
280
  public Vector getSiteList()
281
  {
282
    return this.siteList;
283
  }
284

    
285
  /**
286
   * method to set the list of sites used to constrain this query
287
   */
288
  public void setSiteList(Vector siteList)
289
  {
290
    this.siteList = siteList;
291
  }
292

    
293
  /**
294
   * get the QueryGroup used to express query constraints
295
   */
296
  public QueryGroup getQueryGroup()
297
  {
298
    return query;
299
  }
300

    
301
  /**
302
   * Set up the SAX parser for reading the XML serialized query
303
   */
304
  private XMLReader initializeParser() {
305
    XMLReader parser = null;
306

    
307
    // Set up the SAX document handlers for parsing
308
    try {
309

    
310
      // Get an instance of the parser
311
      parser = XMLReaderFactory.createXMLReader(parserName);
312

    
313
      // Set the ContentHandler to this instance
314
      parser.setContentHandler(this);
315

    
316
      // Set the error Handler to this instance
317
      parser.setErrorHandler(this);
318

    
319
    } catch (Exception e) {
320
       System.err.println("Error in QuerySpcecification.initializeParser " + 
321
                           e.toString());
322
    }
323

    
324
    return parser;
325
  }
326

    
327
  /**
328
   * callback method used by the SAX Parser when the start tag of an 
329
   * element is detected. Used in this context to parse and store
330
   * the query information in class variables.
331
   */
332
  public void startElement (String uri, String localName, 
333
                            String qName, Attributes atts) 
334
         throws SAXException {
335
    BasicNode currentNode = new BasicNode(localName);
336
    // add attributes to BasicNode here
337
    if (atts != null) {
338
      int len = atts.getLength();
339
      for (int i = 0; i < len; i++) {
340
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
341
      }
342
    }
343

    
344
    elementStack.push(currentNode); 
345
    if (currentNode.getTagName().equals("querygroup")) {
346
      QueryGroup currentGroup = new QueryGroup(
347
                                currentNode.getAttribute("operator"));
348
      if (query == null) {
349
        query = currentGroup;
350
      } else {
351
        QueryGroup parentGroup = (QueryGroup)queryStack.peek();
352
        parentGroup.addChild(currentGroup);
353
      }
354
      queryStack.push(currentGroup);
355
    }
356
  }
357

    
358
  /**
359
   * callback method used by the SAX Parser when the end tag of an 
360
   * element is detected. Used in this context to parse and store
361
   * the query information in class variables.
362
   */
363
  public void endElement (String uri, String localName,
364
                          String qName) throws SAXException {
365
    BasicNode leaving = (BasicNode)elementStack.pop(); 
366
    if (leaving.getTagName().equals("queryterm")) {
367
      boolean isCaseSensitive = (new Boolean(
368
              leaving.getAttribute("casesensitive"))).booleanValue();
369
      QueryTerm currentTerm = null;
370
      if (currentPathexpr == null) {
371
        currentTerm = new QueryTerm(isCaseSensitive,
372
                      leaving.getAttribute("searchmode"),currentValue);
373
      } else {
374
        currentTerm = new QueryTerm(isCaseSensitive,
375
                      leaving.getAttribute("searchmode"),currentValue,
376
                      currentPathexpr);
377
      }
378
      QueryGroup currentGroup = (QueryGroup)queryStack.peek();
379
      currentGroup.addChild(currentTerm);
380
      currentValue = null;
381
      currentPathexpr = null;
382
    } else if (leaving.getTagName().equals("querygroup")) {
383
      QueryGroup leavingGroup = (QueryGroup)queryStack.pop();
384
    }
385
  }
386

    
387
  /**
388
   * callback method used by the SAX Parser when the text sequences of an 
389
   * xml stream are detected. Used in this context to parse and store
390
   * the query information in class variables.
391
   */
392
  public void characters(char ch[], int start, int length) {
393

    
394
    String inputString = new String(ch, start, length);
395
    BasicNode currentNode = (BasicNode)elementStack.peek(); 
396
    String currentTag = currentNode.getTagName();
397
    if (currentTag.equals("meta_file_id")) {
398
      meta_file_id = inputString;
399
    } else if (currentTag.equals("querytitle")) {
400
      queryTitle = inputString;
401
    } else if (currentTag.equals("value")) {
402
      currentValue = inputString;
403
    } else if (currentTag.equals("pathexpr")) {
404
      currentPathexpr = inputString;
405
    } else if (currentTag.equals("returndoctype")) {
406
      returnDocList.add(inputString);
407
    } else if (currentTag.equals("filterdoctype")) {
408
      filterDocList.add(inputString);
409
    } else if (currentTag.equals("returnfield")) {
410
      returnFieldList.add(inputString);
411
      containsExtendedSQL = true;
412
    } else if (currentTag.equals("filterdoctype")) {
413
      filterDocList.add(inputString);
414
    } else if (currentTag.equals("owner")) {
415
      ownerList.add(inputString);
416
    } else if (currentTag.equals("site")) {
417
      siteList.add(inputString);
418
    }
419
  }
420

    
421
  /**
422
   * create a SQL serialization of the query that this instance represents
423
   */
424
  public String printSQL(boolean useXMLIndex) {
425
    StringBuffer self = new StringBuffer();
426

    
427
    self.append("SELECT docid,docname,doctype,");
428
    self.append("date_created, date_updated, rev ");
429
    self.append("FROM xml_documents WHERE docid IN (");
430

    
431
    // This determines the documents that meet the query conditions
432
    self.append(query.printSQL(useXMLIndex));
433

    
434
    self.append(") ");
435
 
436
    // Add SQL to filter for doctypes requested in the query
437
    // This is an implicit OR for the list of doctypes. Only doctypes in this
438
    // list will be searched if the tag is present
439
    if (!filterDocList.isEmpty()) {
440
      boolean firstdoctype = true;
441
      self.append(" AND ("); 
442
      Enumeration en = filterDocList.elements();
443
      while (en.hasMoreElements()) {
444
        String currentDoctype = (String)en.nextElement();
445
        if (firstdoctype) {
446
           firstdoctype = false;
447
           self.append(" doctype = '" + currentDoctype + "'"); 
448
        } else {
449
          self.append(" OR doctype = '" + currentDoctype + "'"); 
450
        }
451
      }
452
      self.append(") ");
453
    }
454

    
455
    // Add SQL to filter for owners requested in the query
456
    // This is an implicit OR for the list of owners
457
    if (!ownerList.isEmpty()) {
458
      boolean first = true;
459
      self.append(" AND ("); 
460
      Enumeration en = ownerList.elements();
461
      while (en.hasMoreElements()) {
462
        String current = (String)en.nextElement();
463
        if (first) {
464
           first = false;
465
           self.append(" user_owner = '" + current + "'"); 
466
        } else {
467
          self.append(" OR user_owner = '" + current + "'"); 
468
        }
469
      }
470
      self.append(") ");
471
    }
472

    
473
    // Add SQL to filter for sites requested in the query
474
    // This is an implicit OR for the list of sites
475
    if (!siteList.isEmpty()) {
476
      boolean first = true;
477
      self.append(" AND ("); 
478
      Enumeration en = siteList.elements();
479
      while (en.hasMoreElements()) {
480
        String current = (String)en.nextElement();
481
        if (first) {
482
           first = false;
483
           self.append(" SUBSTR(docid, 1, INSTR(docid, '" +
484
               accNumberSeparator + "')-1) = '" + current + "'"); 
485
        } else {
486
          self.append(" OR SUBSTR(docid, 1, INSTR(docid, '" +
487
               accNumberSeparator + "')-1) = '" + current + "'"); 
488
        }
489
      }
490
      self.append(") ");
491
    }
492
    //System.out.println(self.toString());
493
    return self.toString();
494
  }
495
  
496
  /**
497
   * This method prints sql based upon the &lt;returnfield&gt; tag in the
498
   * pathquery document.  This allows for customization of the 
499
   * returned fields
500
   * @param doclist the list of document ids to search by
501
   */
502
  public String printExtendedSQL(String doclist)
503
  {  
504
    StringBuffer self = new StringBuffer();
505
    self.append("select xml_nodes.docid, xml_index.path, xml_nodes.nodedata ");
506
    self.append("from xml_index, xml_nodes where xml_index.nodeid=");
507
    self.append("xml_nodes.parentnodeid and (xml_index.path like '");
508
    boolean firstfield = true;
509
    //put the returnfields into the query
510
    //the for loop allows for multiple fields
511
    for(int i=0; i<returnFieldList.size(); i++)
512
    {
513
      if(firstfield)
514
      {
515
        firstfield = false;
516
        self.append((String)returnFieldList.elementAt(i));
517
        self.append("' ");
518
      }
519
      else
520
      {
521
        self.append("or xml_index.path like '");
522
        self.append((String)returnFieldList.elementAt(i));
523
        self.append("' ");
524
      }
525
    }
526
    self.append(") AND xml_nodes.docid in (");
527
    //self.append(query.printSQL());
528
    self.append(doclist);
529
    self.append(")");
530
    self.append(" AND xml_nodes.nodetype = 'TEXT'");
531

    
532
    //System.out.println(self.toString());
533
    return self.toString();
534
  }
535
  
536
  public static String printRelationSQL(String docid)
537
  {
538
    StringBuffer self = new StringBuffer();
539
    self.append("select subject, relationship, object, subdoctype, ");
540
    self.append("objdoctype from xml_relation ");
541
    self.append("where docid like '").append(docid).append("'");
542
    return self.toString();
543
  }
544
   
545
  /**
546
   * Prints sql that returns all relations in the database.
547
   */
548
  public static String printPackageSQL()
549
  {
550
    StringBuffer self = new StringBuffer();
551
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
552
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
553
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
554
    self.append("from xml_index where path like ");
555
    self.append("'triple/relationship') rel, ");
556
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
557
    self.append("'triple/object') o, ");
558
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
559
    self.append("where s.parentnodeid = rel.parentnodeid ");
560
    self.append("and rel.parentnodeid = o.parentnodeid ");
561
    self.append("and x.parentnodeid in rel.nodeid ");
562
    self.append("and y.parentnodeid in o.nodeid ");
563
    self.append("and z.parentnodeid in s.nodeid ");
564
    //self.append("and z.nodedata like '%");
565
    //self.append(docid);
566
    //self.append("%'");
567
    return self.toString();
568
  }
569
  
570
  /**
571
   * Prints sql that returns all relations in the database that were input
572
   * under a specific docid
573
   * @param docid the docid to search for.
574
   */
575
  public static String printPackageSQL(String docid)
576
  {
577
    StringBuffer self = new StringBuffer();
578
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
579
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
580
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
581
    self.append("from xml_index where path like ");
582
    self.append("'triple/relationship') rel, ");
583
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
584
    self.append("'triple/object') o, ");
585
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
586
    self.append("where s.parentnodeid = rel.parentnodeid ");
587
    self.append("and rel.parentnodeid = o.parentnodeid ");
588
    self.append("and x.parentnodeid in rel.nodeid ");
589
    self.append("and y.parentnodeid in o.nodeid ");
590
    self.append("and z.parentnodeid in s.nodeid ");
591
    self.append("and z.docid like '").append(docid).append("'");
592
    
593
    return self.toString();
594
  }
595
  
596
  /**
597
   * Returns all of the relations that has a certain docid in the subject
598
   * or the object.
599
   * 
600
   * @param docid the docid to search for
601
   */
602
  public static String printPackageSQL(String subDocidURL, String objDocidURL)
603
  {
604
    StringBuffer self = new StringBuffer();
605
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
606
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
607
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
608
    self.append("from xml_index where path like ");
609
    self.append("'triple/relationship') rel, ");
610
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
611
    self.append("'triple/object') o, ");
612
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
613
    self.append("where s.parentnodeid = rel.parentnodeid ");
614
    self.append("and rel.parentnodeid = o.parentnodeid ");
615
    self.append("and x.parentnodeid in rel.nodeid ");
616
    self.append("and y.parentnodeid in o.nodeid ");
617
    self.append("and z.parentnodeid in s.nodeid ");
618
    self.append("and (z.nodedata like '");
619
    self.append(subDocidURL);
620
    self.append("' or y.nodedata like '");
621
    self.append(objDocidURL);
622
    self.append("')");
623
    return self.toString();
624
  }
625
  
626
  public static String printGetDocByDoctypeSQL(String docid)
627
  {
628
    StringBuffer self = new StringBuffer();
629

    
630
    self.append("SELECT docid,docname,doctype,");
631
    self.append("date_created, date_updated ");
632
    self.append("FROM xml_documents WHERE docid IN (");
633
    self.append(docid).append(")");
634
    return self.toString();
635
  }
636
  
637
  /**
638
   * create a String description of the query that this instance represents.
639
   * This should become a way to get the XML serialization of the query.
640
   */
641
  public String toString() {
642
    return "meta_file_id=" + meta_file_id + "\n" + query;
643
//DOCTITLE attr cleared from the db
644
//    return "meta_file_id=" + meta_file_id + "\n" + 
645
//           "querytitle=" + querytitle + "\n" + query;
646
  }
647

    
648
  /** a utility class that represents a group of terms in a query */
649
  private class QueryGroup {
650
    private String operator = null;  // indicates how query terms are combined
651
    private Vector children = null;  // the list of query terms and groups
652

    
653
    /** 
654
     * construct a new QueryGroup 
655
     *
656
     * @param operator the boolean conector used to connect query terms 
657
     *                    in this query group
658
     */
659
    public QueryGroup(String operator) {
660
      this.operator = operator;
661
      children = new Vector();
662
    }
663

    
664
    /** 
665
     * Add a child QueryGroup to this QueryGroup
666
     *
667
     * @param qgroup the query group to be added to the list of terms
668
     */
669
    public void addChild(QueryGroup qgroup) {
670
      children.add((Object)qgroup); 
671
    }
672

    
673
    /**
674
     * Add a child QueryTerm to this QueryGroup
675
     *
676
     * @param qterm the query term to be added to the list of terms
677
     */
678
    public void addChild(QueryTerm qterm) {
679
      children.add((Object)qterm); 
680
    }
681

    
682
    /**
683
     * Retrieve an Enumeration of query terms for this QueryGroup
684
     */
685
    public Enumeration getChildren() {
686
      return children.elements();
687
    }
688
   
689
    /**
690
     * create a SQL serialization of the query that this instance represents
691
     */
692
    public String printSQL(boolean useXMLIndex) {
693
      StringBuffer self = new StringBuffer();
694
      boolean first = true;
695

    
696
      self.append("(");
697

    
698
      Enumeration en= getChildren();
699
      while (en.hasMoreElements()) {
700
        Object qobject = en.nextElement();
701
        if (first) {
702
          first = false;
703
        } else {
704
          self.append(" " + operator + " ");
705
        }
706
        if (qobject instanceof QueryGroup) {
707
          QueryGroup qg = (QueryGroup)qobject;
708
          self.append(qg.printSQL(useXMLIndex));
709
        } else if (qobject instanceof QueryTerm) {
710
          QueryTerm qt = (QueryTerm)qobject;
711
          self.append(qt.printSQL(useXMLIndex));
712
        } else {
713
          System.err.println("qobject wrong type: fatal error");
714
        }
715
      }
716
      self.append(") \n");
717
      return self.toString();
718
    }
719

    
720
    /**
721
     * create a String description of the query that this instance represents.
722
     * This should become a way to get the XML serialization of the query.
723
     */
724
    public String toString() {
725
      StringBuffer self = new StringBuffer();
726

    
727
      self.append("  (Query group operator=" + operator + "\n");
728
      Enumeration en= getChildren();
729
      while (en.hasMoreElements()) {
730
        Object qobject = en.nextElement();
731
        self.append(qobject);
732
      }
733
      self.append("  )\n");
734
      return self.toString();
735
    }
736
  }
737

    
738
  /** a utility class that represents a single term in a query */
739
  private class QueryTerm {
740
    private boolean casesensitive = false;
741
    private String searchmode = null;
742
    private String value = null;
743
    private String pathexpr = null;
744

    
745
    /**
746
     * Construct a new instance of a query term for a free text search
747
     * (using the value only)
748
     *
749
     * @param casesensitive flag indicating whether case is used to match
750
     * @param searchmode determines what kind of substring match is performed
751
     *        (one of starts-with|ends-with|contains|matches-exactly)
752
     * @param value the text value to match
753
     */
754
    public QueryTerm(boolean casesensitive, String searchmode, 
755
                     String value) {
756
      this.casesensitive = casesensitive;
757
      this.searchmode = searchmode;
758
      this.value = value;
759
    }
760

    
761
    /**
762
     * Construct a new instance of a query term for a structured search
763
     * (matching the value only for those nodes in the pathexpr)
764
     *
765
     * @param casesensitive flag indicating whether case is used to match
766
     * @param searchmode determines what kind of substring match is performed
767
     *        (one of starts-with|ends-with|contains|matches-exactly)
768
     * @param value the text value to match
769
     * @param pathexpr the hierarchical path to the nodes to be searched
770
     */
771
    public QueryTerm(boolean casesensitive, String searchmode, 
772
                     String value, String pathexpr) {
773
      this(casesensitive, searchmode, value);
774
      this.pathexpr = pathexpr;
775
    }
776

    
777
    /** determine if the QueryTerm is case sensitive */
778
    public boolean isCaseSensitive() {
779
      return casesensitive;
780
    }
781

    
782
    /** get the searchmode parameter */
783
    public String getSearchMode() {
784
      return searchmode;
785
    }
786
 
787
    /** get the Value parameter */
788
    public String getValue() {
789
      return value;
790
    }
791

    
792
    /** get the path expression parameter */
793
    public String getPathExpression() {
794
      return pathexpr;
795
    }
796

    
797
    /**
798
     * create a SQL serialization of the query that this instance represents
799
     */
800
    public String printSQL(boolean useXMLIndex) {
801
      StringBuffer self = new StringBuffer();
802

    
803
      // Uppercase the search string if case match is not important
804
      String casevalue = null;
805
      String nodedataterm = null;
806

    
807
      if (casesensitive) {
808
        nodedataterm = "nodedata";
809
        casevalue = value;
810
      } else {
811
        nodedataterm = "UPPER(nodedata)";
812
        casevalue = value.toUpperCase();
813
      }
814

    
815
      // Add appropriate wildcards to search string
816
      //String searchvalue = null;
817
      String searchexpr = null;
818
      if (searchmode.equals("starts-with")) {
819
        //searchvalue = casevalue + "%";
820
        searchexpr = nodedataterm + " LIKE '" + casevalue + "%' ";
821
      } else if (searchmode.equals("ends-with")) {
822
        //searchvalue = "%" + casevalue;
823
        searchexpr = nodedataterm + " LIKE '%" + casevalue + "' ";
824
      } else if (searchmode.equals("contains")) {
825
        //searchvalue = "%" + casevalue + "%";
826
        searchexpr = nodedataterm + " LIKE '%" + casevalue + "%' ";
827
      } else if (searchmode.equals("equals")) {
828
        //searchvalue = casevalue;
829
        searchexpr = nodedataterm + " = '" + casevalue + "' ";
830
      } else if (searchmode.equals("isnot-equal")) {
831
        //searchvalue = casevalue;
832
        searchexpr = nodedataterm + " != '" + casevalue + "' ";
833
      } else { 
834
        //searchvalue = casevalue;
835
        String oper = null;
836
        if (searchmode.equals("greater-than")) {
837
          oper = ">";
838
        } else if (searchmode.equals("greater-than-equals")) {
839
          oper = ">=";
840
        } else if (searchmode.equals("less-than")) {
841
          oper = "<";
842
        } else if (searchmode.equals("less-than-equals")) {
843
          oper = "<=";
844
        } else {
845
          System.out.println("NOT expected case. NOT recognized operator: " +
846
                             searchmode);
847
          return null;
848
        }
849
        try {
850
          // it is number; numeric comparison
851
          searchexpr = nodedataterm + " " + oper + " " +
852
                       new Double(casevalue) + " ";          
853
        } catch (NumberFormatException nfe) {
854
          // these are characters; character comparison
855
          searchexpr = nodedataterm + " " + oper + " '" + casevalue + "' ";
856
        }
857
      }
858

    
859
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
860
      //self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
861
      self.append(searchexpr);
862

    
863
      if (pathexpr != null) {
864
        self.append("AND parentnodeid IN ");
865
        // use XML Index
866
        if ( useXMLIndex ) {
867
          self.append("(SELECT nodeid FROM xml_index WHERE path LIKE " + 
868
                      "'" +  pathexpr + "') " );
869
        // without using XML Index; using nested statements instead
870
        } else {
871
          self.append(useNestedStatements(pathexpr));
872
        }
873
      }
874

    
875
      return self.toString();
876
    }
877

    
878
    /* 
879
     * Constraint the query with @pathexp without using the XML Index,
880
     * but nested SQL statements instead. The query migth be slower.
881
     */
882
    private String useNestedStatements(String pathexpr)
883
    {
884
      StringBuffer nestedStmts = new StringBuffer();
885
      Vector nodes = new Vector();
886
      String path = pathexpr;
887
      int inx = 0;
888

    
889
      do {
890
        inx = path.lastIndexOf("/");
891
//System.out.println(path.substring(inx+1));
892
        nodes.addElement(path.substring(inx+1));
893
        path = path.substring(0, Math.abs(inx));
894
      } while ( inx > 0 );
895
      
896
      // nested statements
897
      int i = 0;
898
      for (i = 0; i < nodes.size()-1; i++) {
899
        nestedStmts.append("(SELECT nodeid FROM xml_nodes" + 
900
                           " WHERE nodename LIKE '" +
901
                             (String)nodes.elementAt(i) + "'" +
902
                           " AND parentnodeid IN ");
903
      }
904
      // for the last statement: it is without " AND parentnodeid IN "
905
      nestedStmts.append("(SELECT nodeid FROM xml_nodes" + 
906
                         " WHERE nodename LIKE '" +
907
                         (String)nodes.elementAt(i) + "'" );
908
      // node.size() number of closing brackets
909
      for (i = 0; i < nodes.size(); i++) {
910
        nestedStmts.append(")");
911
      }
912

    
913

    
914
//System.out.println(nestedStmts.toString());
915
      return nestedStmts.toString();
916
    }
917

    
918
    /**
919
     * create a String description of the query that this instance represents.
920
     * This should become a way to get the XML serialization of the query.
921
     */
922
    public String toString() {
923

    
924
      return this.printSQL(true);
925
    }
926
  }
927
}
(36-36/40)