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: berkley $'
13
 *     '$Date: 2002-01-18 10:24:13 -0800 (Fri, 18 Jan 2002) $'
14
 * '$Revision: 899 $'
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 edu.ucsb.nceas.dbadapter.*;
34

    
35
import java.io.*;
36
import java.util.Stack;
37
import java.util.Vector;
38
import java.util.Enumeration;
39

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

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

    
74
  // Query data structures used temporarily during XML parsing
75
  private Stack elementStack;
76
  private Stack queryStack;
77
  private String currentValue;
78
  private String currentPathexpr;
79
  private String parserName = null;
80
  private String accNumberSeparator = null;
81
  private static final AbstractDatabase dbAdapter = MetaCatUtil.dbAdapter;
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 Reader
89
   * @param parserName the fully qualified name of a Java Class implementing
90
   *                  the org.xml.sax.XMLReader interface
91
   */
92
  public QuerySpecification( Reader queryspec, String parserName,
93
         String accNumberSeparator ) throws IOException {
94
    super();
95
    
96
    // Initialize the class variables
97
    returnDocList = new Vector();
98
    filterDocList = new Vector();
99
    elementStack = new Stack();
100
    queryStack   = new Stack();
101
    returnFieldList = new Vector();
102
    ownerList = new Vector();
103
    siteList = new Vector();
104
    this.parserName = parserName;
105
    this.accNumberSeparator = accNumberSeparator;
106

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

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

    
134
  /** Main routine for testing */
135
  static public void main(String[] args) {
136

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

    
150
       try {
151
         MetaCatUtil util = new MetaCatUtil();
152
         FileReader xml = new FileReader(new File(xmlfile));
153
         QuerySpecification qspec = 
154
                 new QuerySpecification(xml, util.getOption("saxparser"),
155
                                        util.getOption("accNumberSeparator"));
156
         System.out.println(qspec.printSQL(useXMLIndex));
157

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

    
189
  /**
190
   * method to set the identifier of this query
191
   */
192
  public void setIdentifier(String id) {
193
    this.meta_file_id = id;
194
  }
195

    
196
  /**
197
   * Accessor method to return the title of this Query
198
   */
199
  public String getQueryTitle()
200
  {
201
    return queryTitle;
202
  }
203

    
204
  /**
205
   * method to set the title of this query
206
   */
207
  public void setQueryTitle(String title)
208
  {
209
    this.queryTitle = title;
210
  }
211

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

    
221
  /**
222
   * method to set the list of return docs of this query
223
   */
224
  public void setReturnDocList(Vector returnDocList)
225
  {
226
    this.returnDocList = returnDocList;
227
  }
228

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

    
238
  /**
239
   * method to set the list of filter docs of this query
240
   */
241
  public void setFilterDocList(Vector filterDocList)
242
  {
243
    this.filterDocList = filterDocList;
244
  }
245

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

    
255
  /**
256
   * method to set the list of fields to be returned by this query
257
   */
258
  public void setReturnFieldList(Vector returnFieldList)
259
  {
260
    this.returnFieldList = returnFieldList;
261
  }
262

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

    
272
  /**
273
   * method to set the list of owners used to constrain this query
274
   */
275
  public void setOwnerList(Vector ownerList)
276
  {
277
    this.ownerList = ownerList;
278
  }
279

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

    
289
  /**
290
   * method to set the list of sites used to constrain this query
291
   */
292
  public void setSiteList(Vector siteList)
293
  {
294
    this.siteList = siteList;
295
  }
296

    
297
  /**
298
   * get the QueryGroup used to express query constraints
299
   */
300
  public QueryGroup getQueryGroup()
301
  {
302
    return query;
303
  }
304

    
305
  /**
306
   * Set up the SAX parser for reading the XML serialized query
307
   */
308
  private XMLReader initializeParser() {
309
    XMLReader parser = null;
310

    
311
    // Set up the SAX document handlers for parsing
312
    try {
313

    
314
      // Get an instance of the parser
315
      parser = XMLReaderFactory.createXMLReader(parserName);
316

    
317
      // Set the ContentHandler to this instance
318
      parser.setContentHandler(this);
319

    
320
      // Set the error Handler to this instance
321
      parser.setErrorHandler(this);
322

    
323
    } catch (Exception e) {
324
       System.err.println("Error in QuerySpcecification.initializeParser " + 
325
                           e.toString());
326
    }
327

    
328
    return parser;
329
  }
330

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

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

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

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

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

    
425
  /**
426
   * create a SQL serialization of the query that this instance represents
427
   */
428
  public String printSQL(boolean useXMLIndex) {
429
    StringBuffer self = new StringBuffer();
430

    
431
    self.append("SELECT docid,docname,doctype,");
432
    self.append("date_created, date_updated, rev ");
433
    self.append("FROM xml_documents WHERE docid IN (");
434

    
435
    // This determines the documents that meet the query conditions
436
    self.append(query.printSQL(useXMLIndex));
437

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

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

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

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

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

    
652
  /** a utility class that represents a group of terms in a query */
653
  private class QueryGroup {
654
    private String operator = null;  // indicates how query terms are combined
655
    private Vector children = null;  // the list of query terms and groups
656

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

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

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

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

    
700
      self.append("(");
701

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

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

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

    
742
  /** a utility class that represents a single term in a query */
743
  private class QueryTerm {
744
    private boolean casesensitive = false;
745
    private String searchmode = null;
746
    private String value = null;
747
    private String pathexpr = null;
748

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

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

    
781
    /** determine if the QueryTerm is case sensitive */
782
    public boolean isCaseSensitive() {
783
      return casesensitive;
784
    }
785

    
786
    /** get the searchmode parameter */
787
    public String getSearchMode() {
788
      return searchmode;
789
    }
790
 
791
    /** get the Value parameter */
792
    public String getValue() {
793
      return value;
794
    }
795

    
796
    /** get the path expression parameter */
797
    public String getPathExpression() {
798
      return pathexpr;
799
    }
800

    
801
    /**
802
     * create a SQL serialization of the query that this instance represents
803
     */
804
    public String printSQL(boolean useXMLIndex) {
805
      StringBuffer self = new StringBuffer();
806

    
807
      // Uppercase the search string if case match is not important
808
      String casevalue = null;
809
      String nodedataterm = null;
810

    
811
      if (casesensitive) {
812
        nodedataterm = "nodedata";
813
        casevalue = value;
814
      } else {
815
        nodedataterm = "UPPER(nodedata)";
816
        casevalue = value.toUpperCase();
817
      }
818

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

    
863
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
864
      //self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
865
      self.append(searchexpr);
866

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

    
879
      return self.toString();
880
    }
881

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

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

    
917

    
918
//System.out.println(nestedStmts.toString());
919
      return nestedStmts.toString();
920
    }
921

    
922
    /**
923
     * create a String description of the query that this instance represents.
924
     * This should become a way to get the XML serialization of the query.
925
     */
926
    public String toString() {
927

    
928
      return this.printSQL(true);
929
    }
930
  }
931
}
(37-37/41)