Project

General

Profile

1 155 jones
/**
2 203 jones
 *  '$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 349 jones
 *    Release: @release@
11 155 jones
 *
12 203 jones
 *   '$Author$'
13
 *     '$Date$'
14
 * '$Revision$'
15 669 jones
 *
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 155 jones
 */
30
31
package edu.ucsb.nceas.metacat;
32
33
import java.io.*;
34
import java.util.Stack;
35 158 jones
import java.util.Vector;
36 159 jones
import java.util.Enumeration;
37 155 jones
38 185 jones
import org.xml.sax.Attributes;
39 158 jones
import org.xml.sax.InputSource;
40
import org.xml.sax.SAXException;
41
import org.xml.sax.SAXParseException;
42 185 jones
import org.xml.sax.XMLReader;
43
import org.xml.sax.helpers.XMLReaderFactory;
44
import org.xml.sax.helpers.DefaultHandler;
45 155 jones
46 402 berkley
/**
47 172 jones
 * 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 155 jones
 */
51 185 jones
public class QuerySpecification extends DefaultHandler {
52 155 jones
53 402 berkley
  private boolean containsExtendedSQL=false;
54
55 158 jones
  // Query data structures
56
  private String meta_file_id;
57 692 bojilova
// DOCTITLE attr cleared from the db
58
//  private String querytitle;
59 172 jones
  private Vector doctypeList;
60 402 berkley
  private Vector returnFieldList;
61 535 jones
  private Vector ownerList;
62
  private Vector siteList;
63 158 jones
  private QueryGroup query = null;
64
65
  private Stack elementStack;
66
  private Stack queryStack;
67 159 jones
  private String currentValue;
68
  private String currentPathexpr;
69 172 jones
  private String parserName = null;
70 535 jones
  private String accNumberSeparator = null;
71 158 jones
72 155 jones
  /**
73
   * construct an instance of the QuerySpecification class
74
   *
75 172 jones
   * @param queryspec the XML representation of the query (should conform
76
   *                  to pathquery.dtd) as a Reader
77
   * @param parserName the fully qualified name of a Java Class implementing
78 185 jones
   *                  the org.xml.sax.XMLReader interface
79 155 jones
   */
80 535 jones
  public QuerySpecification( Reader queryspec, String parserName,
81
         String accNumberSeparator ) throws IOException {
82 155 jones
    super();
83 402 berkley
84 172 jones
    // Initialize the class variables
85
    doctypeList = new Vector();
86 158 jones
    elementStack = new Stack();
87
    queryStack   = new Stack();
88 402 berkley
    returnFieldList = new Vector();
89 535 jones
    ownerList = new Vector();
90
    siteList = new Vector();
91 172 jones
    this.parserName = parserName;
92 535 jones
    this.accNumberSeparator = accNumberSeparator;
93 158 jones
94
    // Initialize the parser and read the queryspec
95 185 jones
    XMLReader parser = initializeParser();
96 181 jones
    if (parser == null) {
97
      System.err.println("SAX parser not instantiated properly.");
98
    }
99 155 jones
    try {
100
      parser.parse(new InputSource(queryspec));
101
    } catch (SAXException e) {
102 675 berkley
      System.err.println("error parsing data in " +
103
                         "QuerySpecification.QuerySpecification");
104 180 jones
      System.err.println(e.getMessage());
105 155 jones
    }
106
  }
107
108
  /**
109
   * construct an instance of the QuerySpecification class
110
   *
111 172 jones
   * @param queryspec the XML representation of the query (should conform
112
   *                  to pathquery.dtd) as a String
113
   * @param parserName the fully qualified name of a Java Class implementing
114
   *                  the org.xml.sax.Parser interface
115 155 jones
   */
116 535 jones
  public QuerySpecification( String queryspec, String parserName,
117
         String accNumberSeparator) throws IOException {
118
    this(new StringReader(queryspec), parserName, accNumberSeparator);
119 155 jones
  }
120
121
  /** Main routine for testing */
122
  static public void main(String[] args) {
123
124
     if (args.length < 1) {
125
       System.err.println("Wrong number of arguments!!!");
126
       System.err.println("USAGE: java QuerySpecification <xmlfile>");
127
       return;
128
     } else {
129 711 jones
       int i = 0;
130
       boolean useXMLIndex = true;
131
       if ( args[i].equals( "-noindex" ) ) {
132
         useXMLIndex = false;
133
         i++;
134
       }
135
       String xmlfile  = args[i];
136
137 155 jones
       try {
138 203 jones
         MetaCatUtil util = new MetaCatUtil();
139 155 jones
         FileReader xml = new FileReader(new File(xmlfile));
140 203 jones
         QuerySpecification qspec =
141 535 jones
                 new QuerySpecification(xml, util.getOption("saxparser"),
142
                                        util.getOption("accNumberSeparator"));
143 706 bojilova
         System.out.println(qspec.printSQL(useXMLIndex));
144 181 jones
145 155 jones
       } catch (IOException e) {
146
         System.err.println(e.getMessage());
147
       }
148
149
     }
150
  }
151 402 berkley
152
  /**
153
   * Returns true if the parsed query contains and extended xml query
154
   * (i.e. there is at least one &lt;returnfield&gt; in the pathquery document)
155
   */
156
  public boolean containsExtendedSQL()
157
  {
158
    if(containsExtendedSQL)
159
    {
160
      return true;
161
    }
162
    else
163
    {
164
      return false;
165
    }
166
  }
167
168
  /**
169
   * Accessor method to return a vector of the extended return fields as
170
   * defined in the &lt;returnfield&gt; tag in the pathquery dtd.
171
   */
172
  public Vector getReturnFieldList()
173
  {
174
    return this.returnFieldList;
175
  }
176 155 jones
177 172 jones
  /**
178
   * Set up the SAX parser for reading the XML serialized query
179
   */
180 185 jones
  private XMLReader initializeParser() {
181
    XMLReader parser = null;
182 172 jones
183 155 jones
    // Set up the SAX document handlers for parsing
184
    try {
185
186
      // Get an instance of the parser
187 185 jones
      parser = XMLReaderFactory.createXMLReader(parserName);
188 155 jones
189 185 jones
      // Set the ContentHandler to this instance
190
      parser.setContentHandler(this);
191 155 jones
192 185 jones
      // Set the error Handler to this instance
193 158 jones
      parser.setErrorHandler(this);
194 155 jones
195
    } catch (Exception e) {
196 675 berkley
       System.err.println("Error in QuerySpcecification.initializeParser " +
197
                           e.toString());
198 155 jones
    }
199
200
    return parser;
201
  }
202
203 172 jones
  /**
204
   * callback method used by the SAX Parser when the start tag of an
205
   * element is detected. Used in this context to parse and store
206
   * the query information in class variables.
207
   */
208 185 jones
  public void startElement (String uri, String localName,
209
                            String qName, Attributes atts)
210 155 jones
         throws SAXException {
211 185 jones
    BasicNode currentNode = new BasicNode(localName);
212 159 jones
    // add attributes to BasicNode here
213
    if (atts != null) {
214
      int len = atts.getLength();
215
      for (int i = 0; i < len; i++) {
216 185 jones
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
217 159 jones
      }
218
    }
219
220 158 jones
    elementStack.push(currentNode);
221 159 jones
    if (currentNode.getTagName().equals("querygroup")) {
222 158 jones
      QueryGroup currentGroup = new QueryGroup(
223 178 jones
                                currentNode.getAttribute("operator"));
224 158 jones
      if (query == null) {
225
        query = currentGroup;
226 159 jones
      } else {
227
        QueryGroup parentGroup = (QueryGroup)queryStack.peek();
228
        parentGroup.addChild(currentGroup);
229 158 jones
      }
230 159 jones
      queryStack.push(currentGroup);
231 158 jones
    }
232 155 jones
  }
233
234 172 jones
  /**
235
   * callback method used by the SAX Parser when the end tag of an
236
   * element is detected. Used in this context to parse and store
237
   * the query information in class variables.
238
   */
239 185 jones
  public void endElement (String uri, String localName,
240
                          String qName) throws SAXException {
241 158 jones
    BasicNode leaving = (BasicNode)elementStack.pop();
242 159 jones
    if (leaving.getTagName().equals("queryterm")) {
243
      boolean isCaseSensitive = (new Boolean(
244
              leaving.getAttribute("casesensitive"))).booleanValue();
245
      QueryTerm currentTerm = null;
246
      if (currentPathexpr == null) {
247
        currentTerm = new QueryTerm(isCaseSensitive,
248
                      leaving.getAttribute("searchmode"),currentValue);
249
      } else {
250
        currentTerm = new QueryTerm(isCaseSensitive,
251
                      leaving.getAttribute("searchmode"),currentValue,
252
                      currentPathexpr);
253
      }
254
      QueryGroup currentGroup = (QueryGroup)queryStack.peek();
255
      currentGroup.addChild(currentTerm);
256
      currentValue = null;
257
      currentPathexpr = null;
258
    } else if (leaving.getTagName().equals("querygroup")) {
259
      QueryGroup leavingGroup = (QueryGroup)queryStack.pop();
260 158 jones
    }
261 155 jones
  }
262 158 jones
263 172 jones
  /**
264
   * callback method used by the SAX Parser when the text sequences of an
265
   * xml stream are detected. Used in this context to parse and store
266
   * the query information in class variables.
267
   */
268 158 jones
  public void characters(char ch[], int start, int length) {
269
270
    String inputString = new String(ch, start, length);
271
    BasicNode currentNode = (BasicNode)elementStack.peek();
272
    String currentTag = currentNode.getTagName();
273
    if (currentTag.equals("meta_file_id")) {
274
      meta_file_id = inputString;
275 692 bojilova
// DOCTITLE attr cleared from the db
276
//    } else if (currentTag.equals("querytitle")) {
277
//      querytitle = inputString;
278 159 jones
    } else if (currentTag.equals("value")) {
279
      currentValue = inputString;
280
    } else if (currentTag.equals("pathexpr")) {
281
      currentPathexpr = inputString;
282 172 jones
    } else if (currentTag.equals("returndoctype")) {
283
      doctypeList.add(inputString);
284 402 berkley
    } else if (currentTag.equals("returnfield")) {
285
      returnFieldList.add(inputString);
286
      containsExtendedSQL = true;
287 535 jones
    } else if (currentTag.equals("owner")) {
288
      ownerList.add(inputString);
289
    } else if (currentTag.equals("site")) {
290
      siteList.add(inputString);
291 158 jones
    }
292
  }
293
294 172 jones
295
  /**
296
   * create a SQL serialization of the query that this instance represents
297
   */
298 706 bojilova
  public String printSQL(boolean useXMLIndex) {
299 170 jones
    StringBuffer self = new StringBuffer();
300
301 692 bojilova
    self.append("SELECT docid,docname,doctype,");
302 626 berkley
    self.append("date_created, date_updated, rev ");
303 172 jones
    self.append("FROM xml_documents WHERE docid IN (");
304 170 jones
305 178 jones
    // This determines the documents that meet the query conditions
306 706 bojilova
    self.append(query.printSQL(useXMLIndex));
307 172 jones
308
    self.append(") ");
309 402 berkley
310 172 jones
    // Add SQL to filter for doctypes requested in the query
311 535 jones
    // This is an implicit OR for the list of doctypes
312 172 jones
    if (!doctypeList.isEmpty()) {
313
      boolean firstdoctype = true;
314
      self.append(" AND (");
315
      Enumeration en = doctypeList.elements();
316
      while (en.hasMoreElements()) {
317
        String currentDoctype = (String)en.nextElement();
318
        if (firstdoctype) {
319 402 berkley
           firstdoctype = false;
320
           self.append(" doctype = '" + currentDoctype + "'");
321 172 jones
        } else {
322
          self.append(" OR doctype = '" + currentDoctype + "'");
323
        }
324
      }
325
      self.append(") ");
326
    }
327 402 berkley
328 535 jones
    // Add SQL to filter for owners requested in the query
329
    // This is an implicit OR for the list of owners
330
    if (!ownerList.isEmpty()) {
331
      boolean first = true;
332
      self.append(" AND (");
333
      Enumeration en = ownerList.elements();
334
      while (en.hasMoreElements()) {
335
        String current = (String)en.nextElement();
336
        if (first) {
337
           first = false;
338
           self.append(" user_owner = '" + current + "'");
339
        } else {
340
          self.append(" OR user_owner = '" + current + "'");
341
        }
342
      }
343
      self.append(") ");
344
    }
345
346
    // Add SQL to filter for sites requested in the query
347
    // This is an implicit OR for the list of sites
348
    if (!siteList.isEmpty()) {
349
      boolean first = true;
350
      self.append(" AND (");
351
      Enumeration en = siteList.elements();
352
      while (en.hasMoreElements()) {
353
        String current = (String)en.nextElement();
354
        if (first) {
355
           first = false;
356
           self.append(" SUBSTR(docid, 1, INSTR(docid, '" +
357
               accNumberSeparator + "')-1) = '" + current + "'");
358
        } else {
359
          self.append(" OR SUBSTR(docid, 1, INSTR(docid, '" +
360
               accNumberSeparator + "')-1) = '" + current + "'");
361
        }
362
      }
363
      self.append(") ");
364
    }
365 710 berkley
    //System.out.println(self.toString());
366 170 jones
    return self.toString();
367
  }
368 402 berkley
369
  /**
370
   * This method prints sql based upon the &lt;returnfield&gt; tag in the
371
   * pathquery document.  This allows for customization of the
372
   * returned fields
373 465 berkley
   * @param doclist the list of document ids to search by
374 402 berkley
   */
375 465 berkley
  public String printExtendedSQL(String doclist)
376 402 berkley
  {
377
    StringBuffer self = new StringBuffer();
378 423 berkley
    self.append("select xml_nodes.docid, xml_index.path, xml_nodes.nodedata ");
379
    self.append("from xml_index, xml_nodes where xml_index.nodeid=");
380
    self.append("xml_nodes.parentnodeid and (xml_index.path like '");
381 402 berkley
    boolean firstfield = true;
382
    //put the returnfields into the query
383
    //the for loop allows for multiple fields
384
    for(int i=0; i<returnFieldList.size(); i++)
385
    {
386
      if(firstfield)
387
      {
388
        firstfield = false;
389 405 berkley
        self.append((String)returnFieldList.elementAt(i));
390 402 berkley
        self.append("' ");
391
      }
392
      else
393
      {
394 423 berkley
        self.append("or xml_index.path like '");
395 405 berkley
        self.append((String)returnFieldList.elementAt(i));
396 402 berkley
        self.append("' ");
397
      }
398
    }
399 423 berkley
    self.append(") AND xml_nodes.docid in (");
400 465 berkley
    //self.append(query.printSQL());
401
    self.append(doclist);
402 402 berkley
    self.append(")");
403 423 berkley
    self.append(" AND xml_nodes.nodetype = 'TEXT'");
404 405 berkley
405 423 berkley
    //System.out.println(self.toString());
406 402 berkley
    return self.toString();
407
  }
408 465 berkley
409
  public static String printRelationSQL(String docid)
410
  {
411
    StringBuffer self = new StringBuffer();
412 489 berkley
    self.append("select subject, relationship, object, subdoctype, ");
413
    self.append("objdoctype from xml_relation ");
414 465 berkley
    self.append("where subject like '").append(docid).append("'");
415
    return self.toString();
416
  }
417 453 berkley
418 172 jones
  /**
419 465 berkley
   * Prints sql that returns all relations in the database.
420 453 berkley
   */
421
  public static String printPackageSQL()
422
  {
423
    StringBuffer self = new StringBuffer();
424
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
425
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
426
    self.append("'package/relation/subject') s, (select nodeid, parentnodeid ");
427
    self.append("from xml_index where path like ");
428
    self.append("'package/relation/relationship') rel, ");
429
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
430
    self.append("'package/relation/object') o, ");
431
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
432
    self.append("where s.parentnodeid = rel.parentnodeid ");
433
    self.append("and rel.parentnodeid = o.parentnodeid ");
434
    self.append("and x.parentnodeid in rel.nodeid ");
435
    self.append("and y.parentnodeid in o.nodeid ");
436
    self.append("and z.parentnodeid in s.nodeid ");
437
    //self.append("and z.nodedata like '%");
438
    //self.append(docid);
439
    //self.append("%'");
440
    return self.toString();
441
  }
442
443
  /**
444 465 berkley
   * Prints sql that returns all relations in the database that were input
445
   * under a specific docid
446
   * @param docid the docid to search for.
447
   */
448
  public static String printPackageSQL(String docid)
449
  {
450
    StringBuffer self = new StringBuffer();
451
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
452
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
453
    self.append("'package/relation/subject') s, (select nodeid, parentnodeid ");
454
    self.append("from xml_index where path like ");
455
    self.append("'package/relation/relationship') rel, ");
456
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
457
    self.append("'package/relation/object') o, ");
458
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
459
    self.append("where s.parentnodeid = rel.parentnodeid ");
460
    self.append("and rel.parentnodeid = o.parentnodeid ");
461
    self.append("and x.parentnodeid in rel.nodeid ");
462
    self.append("and y.parentnodeid in o.nodeid ");
463
    self.append("and z.parentnodeid in s.nodeid ");
464
    self.append("and z.docid like '").append(docid).append("'");
465
466
    return self.toString();
467
  }
468
469
  /**
470
   * Returns all of the relations that has a certain docid in the subject
471
   * or the object.
472
   *
473
   * @param docid the docid to search for
474
   */
475
  public static String printPackageSQL(String subDocidURL, String objDocidURL)
476
  {
477
    StringBuffer self = new StringBuffer();
478
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
479
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
480
    self.append("'package/relation/subject') s, (select nodeid, parentnodeid ");
481
    self.append("from xml_index where path like ");
482
    self.append("'package/relation/relationship') rel, ");
483
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
484
    self.append("'package/relation/object') o, ");
485
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
486
    self.append("where s.parentnodeid = rel.parentnodeid ");
487
    self.append("and rel.parentnodeid = o.parentnodeid ");
488
    self.append("and x.parentnodeid in rel.nodeid ");
489
    self.append("and y.parentnodeid in o.nodeid ");
490
    self.append("and z.parentnodeid in s.nodeid ");
491
    self.append("and (z.nodedata like '");
492
    self.append(subDocidURL);
493
    self.append("' or y.nodedata like '");
494
    self.append(objDocidURL);
495
    self.append("')");
496
    return self.toString();
497
  }
498
499
  public static String printGetDocByDoctypeSQL(String docid)
500
  {
501
    StringBuffer self = new StringBuffer();
502
503 692 bojilova
    self.append("SELECT docid,docname,doctype,");
504 465 berkley
    self.append("date_created, date_updated ");
505
    self.append("FROM xml_documents WHERE docid IN (");
506
    self.append(docid).append(")");
507
    return self.toString();
508
  }
509
510
  /**
511 172 jones
   * create a String description of the query that this instance represents.
512
   * This should become a way to get the XML serialization of the query.
513
   */
514 159 jones
  public String toString() {
515 692 bojilova
    return "meta_file_id=" + meta_file_id + "\n" + query;
516
//DOCTITLE attr cleared from the db
517
//    return "meta_file_id=" + meta_file_id + "\n" +
518
//           "querytitle=" + querytitle + "\n" + query;
519 159 jones
  }
520
521 158 jones
  /** a utility class that represents a group of terms in a query */
522
  private class QueryGroup {
523 178 jones
    private String operator = null;  // indicates how query terms are combined
524
    private Vector children = null;  // the list of query terms and groups
525 158 jones
526 172 jones
    /**
527
     * construct a new QueryGroup
528
     *
529 178 jones
     * @param operator the boolean conector used to connect query terms
530 172 jones
     *                    in this query group
531
     */
532 178 jones
    public QueryGroup(String operator) {
533
      this.operator = operator;
534 158 jones
      children = new Vector();
535
    }
536
537 172 jones
    /**
538
     * Add a child QueryGroup to this QueryGroup
539
     *
540
     * @param qgroup the query group to be added to the list of terms
541
     */
542 158 jones
    public void addChild(QueryGroup qgroup) {
543
      children.add((Object)qgroup);
544
    }
545
546 172 jones
    /**
547
     * Add a child QueryTerm to this QueryGroup
548
     *
549
     * @param qterm the query term to be added to the list of terms
550
     */
551 158 jones
    public void addChild(QueryTerm qterm) {
552
      children.add((Object)qterm);
553
    }
554
555 172 jones
    /**
556
     * Retrieve an Enumeration of query terms for this QueryGroup
557
     */
558 158 jones
    public Enumeration getChildren() {
559
      return children.elements();
560
    }
561 159 jones
562 172 jones
    /**
563
     * create a SQL serialization of the query that this instance represents
564
     */
565 706 bojilova
    public String printSQL(boolean useXMLIndex) {
566 170 jones
      StringBuffer self = new StringBuffer();
567
      boolean first = true;
568
569
      self.append("(");
570
571
      Enumeration en= getChildren();
572
      while (en.hasMoreElements()) {
573
        Object qobject = en.nextElement();
574
        if (first) {
575
          first = false;
576
        } else {
577 178 jones
          self.append(" " + operator + " ");
578 170 jones
        }
579
        if (qobject instanceof QueryGroup) {
580
          QueryGroup qg = (QueryGroup)qobject;
581 706 bojilova
          self.append(qg.printSQL(useXMLIndex));
582 170 jones
        } else if (qobject instanceof QueryTerm) {
583
          QueryTerm qt = (QueryTerm)qobject;
584 706 bojilova
          self.append(qt.printSQL(useXMLIndex));
585 170 jones
        } else {
586
          System.err.println("qobject wrong type: fatal error");
587
        }
588
      }
589
      self.append(") \n");
590
      return self.toString();
591
    }
592
593 172 jones
    /**
594
     * create a String description of the query that this instance represents.
595
     * This should become a way to get the XML serialization of the query.
596
     */
597 159 jones
    public String toString() {
598
      StringBuffer self = new StringBuffer();
599
600 178 jones
      self.append("  (Query group operator=" + operator + "\n");
601 159 jones
      Enumeration en= getChildren();
602
      while (en.hasMoreElements()) {
603
        Object qobject = en.nextElement();
604
        self.append(qobject);
605
      }
606
      self.append("  )\n");
607
      return self.toString();
608
    }
609 158 jones
  }
610
611
  /** a utility class that represents a single term in a query */
612
  private class QueryTerm {
613
    private boolean casesensitive = false;
614
    private String searchmode = null;
615
    private String value = null;
616
    private String pathexpr = null;
617
618 172 jones
    /**
619
     * Construct a new instance of a query term for a free text search
620
     * (using the value only)
621
     *
622
     * @param casesensitive flag indicating whether case is used to match
623
     * @param searchmode determines what kind of substring match is performed
624
     *        (one of starts-with|ends-with|contains|matches-exactly)
625
     * @param value the text value to match
626
     */
627 158 jones
    public QueryTerm(boolean casesensitive, String searchmode,
628
                     String value) {
629
      this.casesensitive = casesensitive;
630
      this.searchmode = searchmode;
631
      this.value = value;
632
    }
633
634 172 jones
    /**
635
     * Construct a new instance of a query term for a structured search
636
     * (matching the value only for those nodes in the pathexpr)
637
     *
638
     * @param casesensitive flag indicating whether case is used to match
639
     * @param searchmode determines what kind of substring match is performed
640
     *        (one of starts-with|ends-with|contains|matches-exactly)
641
     * @param value the text value to match
642
     * @param pathexpr the hierarchical path to the nodes to be searched
643
     */
644 158 jones
    public QueryTerm(boolean casesensitive, String searchmode,
645
                     String value, String pathexpr) {
646
      this(casesensitive, searchmode, value);
647
      this.pathexpr = pathexpr;
648
    }
649
650 172 jones
    /** determine if the QueryTerm is case sensitive */
651 158 jones
    public boolean isCaseSensitive() {
652
      return casesensitive;
653
    }
654
655 172 jones
    /** get the searchmode parameter */
656 158 jones
    public String getSearchMode() {
657
      return searchmode;
658
    }
659
660 172 jones
    /** get the Value parameter */
661 158 jones
    public String getValue() {
662
      return value;
663
    }
664
665 172 jones
    /** get the path expression parameter */
666 158 jones
    public String getPathExpression() {
667
      return pathexpr;
668
    }
669 159 jones
670 172 jones
    /**
671
     * create a SQL serialization of the query that this instance represents
672
     */
673 706 bojilova
    public String printSQL(boolean useXMLIndex) {
674 170 jones
      StringBuffer self = new StringBuffer();
675
676
      // Uppercase the search string if case match is not important
677
      String casevalue = null;
678
      String nodedataterm = null;
679
680
      if (casesensitive) {
681
        nodedataterm = "nodedata";
682
        casevalue = value;
683
      } else {
684
        nodedataterm = "UPPER(nodedata)";
685
        casevalue = value.toUpperCase();
686
      }
687
688
      // Add appropriate wildcards to search string
689
      String searchvalue = null;
690
      if (searchmode.equals("starts-with")) {
691
        searchvalue = casevalue + "%";
692
      } else if (searchmode.equals("ends-with")) {
693
        searchvalue = "%" + casevalue;
694
      } else if (searchmode.equals("contains")) {
695
        searchvalue = "%" + casevalue + "%";
696
      } else {
697
        searchvalue = casevalue;
698
      }
699
700 178 jones
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
701 170 jones
702
      if (pathexpr != null) {
703
        self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
704
        self.append("AND parentnodeid IN ");
705 706 bojilova
        // use XML Index
706
        if ( useXMLIndex ) {
707
          self.append("(SELECT nodeid FROM xml_index WHERE path LIKE " +
708
                      "'" +  pathexpr + "') " );
709
        // without using XML Index; using nested statements instead
710
        } else {
711
          self.append(useNestedStatements(pathexpr));
712
        }
713 170 jones
      } else {
714
        self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
715
      }
716
717
      return self.toString();
718
    }
719
720 706 bojilova
    /*
721
     * Constraint the query with @pathexp without using the XML Index,
722
     * but nested SQL statements instead. The query migth be slower.
723
     */
724
    private String useNestedStatements(String pathexpr)
725
    {
726
      StringBuffer nestedStmts = new StringBuffer();
727
      Vector nodes = new Vector();
728
      String path = pathexpr;
729
      int inx = 0;
730
731
      do {
732
        inx = path.lastIndexOf("/");
733
//System.out.println(path.substring(inx+1));
734
        nodes.addElement(path.substring(inx+1));
735
        path = path.substring(0, Math.abs(inx));
736
      } while ( inx > 0 );
737
738
      // nested statements
739
      int i = 0;
740
      for (i = 0; i < nodes.size()-1; i++) {
741
        nestedStmts.append("(SELECT nodeid FROM xml_nodes" +
742
                           " WHERE nodename LIKE '" +
743
                             (String)nodes.elementAt(i) + "'" +
744
                           " AND parentnodeid IN ");
745
      }
746
      // for the last statement: it is without " AND parentnodeid IN "
747
      nestedStmts.append("(SELECT nodeid FROM xml_nodes" +
748
                         " WHERE nodename LIKE '" +
749
                         (String)nodes.elementAt(i) + "'" );
750
      // node.size() number of closing brackets
751
      for (i = 0; i < nodes.size(); i++) {
752
        nestedStmts.append(")");
753
      }
754
755
756 710 berkley
//System.out.println(nestedStmts.toString());
757 706 bojilova
      return nestedStmts.toString();
758
    }
759
760 172 jones
    /**
761
     * create a String description of the query that this instance represents.
762
     * This should become a way to get the XML serialization of the query.
763
     */
764 709 bojilova
    public String toString() {
765 159 jones
766 709 bojilova
      return this.printSQL(true);
767 159 jones
    }
768 158 jones
  }
769 155 jones
}