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 899 berkley
import edu.ucsb.nceas.dbadapter.*;
34
35 155 jones
import java.io.*;
36 1354 tao
import java.util.Hashtable;
37 155 jones
import java.util.Stack;
38 158 jones
import java.util.Vector;
39 159 jones
import java.util.Enumeration;
40 155 jones
41 185 jones
import org.xml.sax.Attributes;
42 158 jones
import org.xml.sax.InputSource;
43
import org.xml.sax.SAXException;
44
import org.xml.sax.SAXParseException;
45 185 jones
import org.xml.sax.XMLReader;
46
import org.xml.sax.helpers.XMLReaderFactory;
47
import org.xml.sax.helpers.DefaultHandler;
48 155 jones
49 402 berkley
/**
50 172 jones
 * A Class that represents a structured query, and can be
51
 * constructed from an XML serialization conforming to @see pathquery.dtd.
52
 * The printSQL() method can be used to print a SQL serialization of the query.
53 155 jones
 */
54 185 jones
public class QuerySpecification extends DefaultHandler {
55 155 jones
56 745 jones
  /** flag determining whether extended query terms are present */
57 402 berkley
  private boolean containsExtendedSQL=false;
58 745 jones
  /** Identifier for this query document */
59 158 jones
  private String meta_file_id;
60 745 jones
  /** Title of this query */
61
  private String queryTitle;
62
  /** List of document types to be returned using package back tracing */
63 743 jones
  private Vector returnDocList;
64 745 jones
  /** List of document types to be searched */
65 743 jones
  private Vector filterDocList;
66 745 jones
  /** List of fields to be returned in result set */
67 402 berkley
  private Vector returnFieldList;
68 745 jones
  /** List of users owning documents to be searched */
69 535 jones
  private Vector ownerList;
70 745 jones
  /** List of sites/scopes used to constrain search */
71 535 jones
  private Vector siteList;
72 745 jones
  /** The root query group that contains the recursive query constraints */
73 158 jones
  private QueryGroup query = null;
74 1348 tao
75 745 jones
  // Query data structures used temporarily during XML parsing
76 158 jones
  private Stack elementStack;
77
  private Stack queryStack;
78 159 jones
  private String currentValue;
79
  private String currentPathexpr;
80 172 jones
  private String parserName = null;
81 535 jones
  private String accNumberSeparator = null;
82 899 berkley
  private static final AbstractDatabase dbAdapter = MetaCatUtil.dbAdapter;
83 1301 tao
84 1350 tao
  private int countPercentageSearchItem = 0;
85
  private boolean percentageSearch = false;
86
87 1301 tao
  private String userName = null;
88
  private static final String PUBLIC = "public";
89
  private String [] group = null;
90 158 jones
91 1348 tao
  public static final String ATTRIBUTESYMBOL = "@";
92 1354 tao
  private boolean hasAttributeReturnField = false;
93
  private Hashtable attributeReturnList = new Hashtable();
94
  private int countAttributeReturnField = 0;
95 155 jones
  /**
96
   * construct an instance of the QuerySpecification class
97
   *
98 172 jones
   * @param queryspec the XML representation of the query (should conform
99
   *                  to pathquery.dtd) as a Reader
100
   * @param parserName the fully qualified name of a Java Class implementing
101 185 jones
   *                  the org.xml.sax.XMLReader interface
102 155 jones
   */
103 535 jones
  public QuerySpecification( Reader queryspec, String parserName,
104
         String accNumberSeparator ) throws IOException {
105 155 jones
    super();
106 402 berkley
107 172 jones
    // Initialize the class variables
108 743 jones
    returnDocList = new Vector();
109
    filterDocList = new Vector();
110 158 jones
    elementStack = new Stack();
111
    queryStack   = new Stack();
112 402 berkley
    returnFieldList = new Vector();
113 535 jones
    ownerList = new Vector();
114
    siteList = new Vector();
115 172 jones
    this.parserName = parserName;
116 535 jones
    this.accNumberSeparator = accNumberSeparator;
117 158 jones
118
    // Initialize the parser and read the queryspec
119 185 jones
    XMLReader parser = initializeParser();
120 181 jones
    if (parser == null) {
121
      System.err.println("SAX parser not instantiated properly.");
122
    }
123 155 jones
    try {
124
      parser.parse(new InputSource(queryspec));
125
    } catch (SAXException e) {
126 675 berkley
      System.err.println("error parsing data in " +
127
                         "QuerySpecification.QuerySpecification");
128 180 jones
      System.err.println(e.getMessage());
129 155 jones
    }
130
  }
131
132
  /**
133
   * construct an instance of the QuerySpecification class
134
   *
135 172 jones
   * @param queryspec the XML representation of the query (should conform
136
   *                  to pathquery.dtd) as a String
137
   * @param parserName the fully qualified name of a Java Class implementing
138
   *                  the org.xml.sax.Parser interface
139 155 jones
   */
140 535 jones
  public QuerySpecification( String queryspec, String parserName,
141
         String accNumberSeparator) throws IOException {
142
    this(new StringReader(queryspec), parserName, accNumberSeparator);
143 155 jones
  }
144 1301 tao
145
  /**
146
   * Method to set user name
147
   *
148
   * @param myName  the user name
149
   */
150
  public void setUserName(String myName)
151
  {
152
    this.userName = myName;
153
  }
154
155
  /**
156
   * Method to set user group
157
   *
158
   * @param myGroup  the user group
159
   */
160
  public void setGroup(String [] myGroup)
161
  {
162
    this.group = myGroup;
163
  }
164 1350 tao
  /**
165
   * Method to indicate this query is a percentage search
166
   */
167
  public boolean isPercentageSearch()
168
  {
169
    return percentageSearch;
170
  }
171 1301 tao
  /*
172
   * Method to get owner query. If it is owner it has all permission
173
   */
174
  private String createOwerQuery()
175
  {
176
    String ownerQuery = null;
177
    ownerQuery = "SELECT docid FROM xml_documents WHERE user_owner ='" +
178
                  PUBLIC + "'";
179
    if (userName != null && !userName.equals(""))
180
    {
181
      ownerQuery = ownerQuery + " OR user_owner ='"+ userName +"'";
182
    }
183
184
    if (group != null)
185
    {
186
      for (int i = 0; i< group.length; i++)
187
      {
188
        String groupUint = group[i];
189
        if (groupUint != null && !groupUint.equals(""))
190
        {
191
          ownerQuery = ownerQuery +" OR user_owner = '" + groupUint + "'";
192
        }//if
193
      }//for
194
    }
195
    MetaCatUtil.debugMessage("OwnerQuery: "+ownerQuery, 30);
196
    return ownerQuery;
197
  }
198
199
  /*
200
   * Method to create query for xml_access, this part is to get docid list which
201
   * have a allow rule for a given user
202
   */
203
  private String createAllowRuleQuery()
204
  {
205
    String allowQuery = null;
206
    allowQuery ="SELECT docid from xml_access WHERE ";
207
    // add allow rule for user name
208
    if (userName != null && !userName.equals(""))
209
    {
210
      allowQuery = allowQuery +"(principal_name = '" + userName
211 1304 tao
                              +"' AND perm_type = 'allow'"
212
                              +" AND (permission='4' OR permission='7'))";
213 1301 tao
    }
214
    // add allow rule for public
215
    allowQuery = allowQuery +"OR (principal_name = '" + PUBLIC
216 1304 tao
                              +"' AND perm_type = 'allow'"
217
                              +" AND (permission='4' OR permission='7'))";
218 1301 tao
219
    // add allow rule for group
220
    if (group != null)
221
    {
222
      for (int i = 0; i< group.length; i++)
223
      {
224
        String groupUint = group[i];
225
        if (groupUint != null && !groupUint.equals(""))
226
        {
227
          allowQuery = allowQuery +" OR (principal_name = '" + groupUint
228 1304 tao
                              +"' AND perm_type = 'allow'"
229
                              +" AND (permission='4' OR permission='7'))";
230 1301 tao
        }//if
231
      }//for
232
    }//if
233
    MetaCatUtil.debugMessage("allow query is: "+ allowQuery, 30);
234
    return allowQuery;
235
236
  }
237 155 jones
238 1301 tao
   /*
239
   * Method to create query for xml_access, this part is to get docid list which
240
   * have a deny rule and perm_order is allowFirst for a given user. This means
241
   * the user will be denied to read
242
   */
243
  private String createDenyRuleQuery()
244
  {
245
    String denyQuery = null;
246
    denyQuery ="SELECT docid from xml_access WHERE ";
247
    // add deny rule for user name
248
    if (userName != null && !userName.equals(""))
249
    {
250
      denyQuery = denyQuery +"(principal_name = '" + userName
251
                              +"' AND perm_type = 'deny' "
252 1304 tao
                              +"AND perm_order ='allowFirst'"
253
                              +" AND (permission='4' OR permission='7'))";
254 1301 tao
    }
255
    // add deny rule for public
256
    denyQuery = denyQuery +"OR (principal_name = '" + PUBLIC
257
                               +"' AND perm_type = 'deny' "
258 1304 tao
                               +"AND perm_order ='allowFirst'"
259
                               +" AND (permission='4' OR permission='7'))";
260 1301 tao
261
    // add allow rule for group
262
    if (group != null)
263
    {
264
      for (int i = 0; i< group.length; i++)
265
      {
266
        String groupUint = group[i];
267
        if (groupUint != null && !groupUint.equals(""))
268
        {
269
          denyQuery = denyQuery +" OR (principal_name = '" + groupUint
270
                                +"' AND perm_type = 'deny' "
271 1304 tao
                                +"AND perm_order ='allowFirst'"
272
                                +" AND (permission='4' OR permission='7'))";
273 1301 tao
        }//if
274
      }//for
275
    }//if
276
    MetaCatUtil.debugMessage("denyquery is: "+ denyQuery, 30);
277
    return denyQuery;
278
279
  }
280
281
  /**
282
   * Method to append a access control query to SQL. So in DBQuery class, we can
283
   * get docid from both user specified query and access control query. We don't
284
   * need to checking permission after we get the doclist. It will be good to
285
   * performance
286
   *
287
   */
288
  public String getAccessQuery()
289
  {
290
    String accessQuery = null;
291
    String onwer = createOwerQuery();
292
    String allow = createAllowRuleQuery();
293
    String deny = createDenyRuleQuery();
294
    accessQuery = " AND (docid IN("+ onwer + ")";
295
    accessQuery = accessQuery + " OR (docid IN (" + allow + ")"
296
                 + " AND docid NOT IN ("+ deny + ")))";
297
    MetaCatUtil.debugMessage("accessquery is: "+ accessQuery, 30);
298
    return accessQuery;
299
  }
300
301 155 jones
  /** Main routine for testing */
302
  static public void main(String[] args) {
303
304
     if (args.length < 1) {
305
       System.err.println("Wrong number of arguments!!!");
306
       System.err.println("USAGE: java QuerySpecification <xmlfile>");
307
       return;
308
     } else {
309 711 jones
       int i = 0;
310
       boolean useXMLIndex = true;
311
       if ( args[i].equals( "-noindex" ) ) {
312
         useXMLIndex = false;
313
         i++;
314
       }
315
       String xmlfile  = args[i];
316
317 155 jones
       try {
318 203 jones
         MetaCatUtil util = new MetaCatUtil();
319 155 jones
         FileReader xml = new FileReader(new File(xmlfile));
320 203 jones
         QuerySpecification qspec =
321 535 jones
                 new QuerySpecification(xml, util.getOption("saxparser"),
322
                                        util.getOption("accNumberSeparator"));
323 706 bojilova
         System.out.println(qspec.printSQL(useXMLIndex));
324 181 jones
325 155 jones
       } catch (IOException e) {
326
         System.err.println(e.getMessage());
327
       }
328
329
     }
330
  }
331 402 berkley
332
  /**
333
   * Returns true if the parsed query contains and extended xml query
334
   * (i.e. there is at least one &lt;returnfield&gt; in the pathquery document)
335
   */
336
  public boolean containsExtendedSQL()
337
  {
338
    if(containsExtendedSQL)
339
    {
340
      return true;
341
    }
342
    else
343
    {
344
      return false;
345
    }
346
  }
347
348
  /**
349 1354 tao
   * A method to get if the query has an attribute return field
350
   */
351
  public boolean containAttributeReturnField()
352
  {
353
    return hasAttributeReturnField;
354
  }
355
356
  /**
357 745 jones
   * Accessor method to return the identifier of this Query
358
   */
359
  public String getIdentifier()
360
  {
361
    return meta_file_id;
362
  }
363
364
  /**
365
   * method to set the identifier of this query
366
   */
367
  public void setIdentifier(String id) {
368
    this.meta_file_id = id;
369
  }
370
371
  /**
372
   * Accessor method to return the title of this Query
373
   */
374
  public String getQueryTitle()
375
  {
376
    return queryTitle;
377
  }
378
379
  /**
380
   * method to set the title of this query
381
   */
382
  public void setQueryTitle(String title)
383
  {
384
    this.queryTitle = title;
385
  }
386
387
  /**
388
   * Accessor method to return a vector of the return document types as
389
   * defined in the &lt;returndoctype&gt; tag in the pathquery dtd.
390
   */
391
  public Vector getReturnDocList()
392
  {
393
    return this.returnDocList;
394
  }
395
396
  /**
397
   * method to set the list of return docs of this query
398
   */
399
  public void setReturnDocList(Vector returnDocList)
400
  {
401
    this.returnDocList = returnDocList;
402
  }
403
404
  /**
405
   * Accessor method to return a vector of the filter doc types as
406
   * defined in the &lt;filterdoctype&gt; tag in the pathquery dtd.
407
   */
408
  public Vector getFilterDocList()
409
  {
410
    return this.filterDocList;
411
  }
412
413
  /**
414
   * method to set the list of filter docs of this query
415
   */
416
  public void setFilterDocList(Vector filterDocList)
417
  {
418
    this.filterDocList = filterDocList;
419
  }
420
421
  /**
422 402 berkley
   * Accessor method to return a vector of the extended return fields as
423
   * defined in the &lt;returnfield&gt; tag in the pathquery dtd.
424
   */
425
  public Vector getReturnFieldList()
426
  {
427
    return this.returnFieldList;
428
  }
429 155 jones
430 172 jones
  /**
431 745 jones
   * method to set the list of fields to be returned by this query
432
   */
433
  public void setReturnFieldList(Vector returnFieldList)
434
  {
435
    this.returnFieldList = returnFieldList;
436
  }
437
438
  /**
439
   * Accessor method to return a vector of the owner fields as
440
   * defined in the &lt;owner&gt; tag in the pathquery dtd.
441
   */
442
  public Vector getOwnerList()
443
  {
444
    return this.ownerList;
445
  }
446
447
  /**
448
   * method to set the list of owners used to constrain this query
449
   */
450
  public void setOwnerList(Vector ownerList)
451
  {
452
    this.ownerList = ownerList;
453
  }
454
455
  /**
456
   * Accessor method to return a vector of the site fields as
457
   * defined in the &lt;site&gt; tag in the pathquery dtd.
458
   */
459
  public Vector getSiteList()
460
  {
461
    return this.siteList;
462
  }
463
464
  /**
465
   * method to set the list of sites used to constrain this query
466
   */
467
  public void setSiteList(Vector siteList)
468
  {
469
    this.siteList = siteList;
470
  }
471
472
  /**
473
   * get the QueryGroup used to express query constraints
474
   */
475
  public QueryGroup getQueryGroup()
476
  {
477
    return query;
478
  }
479
480
  /**
481 172 jones
   * Set up the SAX parser for reading the XML serialized query
482
   */
483 185 jones
  private XMLReader initializeParser() {
484
    XMLReader parser = null;
485 172 jones
486 155 jones
    // Set up the SAX document handlers for parsing
487
    try {
488
489
      // Get an instance of the parser
490 185 jones
      parser = XMLReaderFactory.createXMLReader(parserName);
491 155 jones
492 185 jones
      // Set the ContentHandler to this instance
493
      parser.setContentHandler(this);
494 155 jones
495 185 jones
      // Set the error Handler to this instance
496 158 jones
      parser.setErrorHandler(this);
497 155 jones
498
    } catch (Exception e) {
499 675 berkley
       System.err.println("Error in QuerySpcecification.initializeParser " +
500
                           e.toString());
501 155 jones
    }
502
503
    return parser;
504
  }
505
506 172 jones
  /**
507
   * callback method used by the SAX Parser when the start tag of an
508
   * element is detected. Used in this context to parse and store
509
   * the query information in class variables.
510
   */
511 185 jones
  public void startElement (String uri, String localName,
512
                            String qName, Attributes atts)
513 155 jones
         throws SAXException {
514 185 jones
    BasicNode currentNode = new BasicNode(localName);
515 159 jones
    // add attributes to BasicNode here
516
    if (atts != null) {
517
      int len = atts.getLength();
518
      for (int i = 0; i < len; i++) {
519 185 jones
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
520 159 jones
      }
521
    }
522
523 158 jones
    elementStack.push(currentNode);
524 159 jones
    if (currentNode.getTagName().equals("querygroup")) {
525 158 jones
      QueryGroup currentGroup = new QueryGroup(
526 178 jones
                                currentNode.getAttribute("operator"));
527 158 jones
      if (query == null) {
528
        query = currentGroup;
529 159 jones
      } else {
530
        QueryGroup parentGroup = (QueryGroup)queryStack.peek();
531
        parentGroup.addChild(currentGroup);
532 158 jones
      }
533 159 jones
      queryStack.push(currentGroup);
534 158 jones
    }
535 155 jones
  }
536
537 172 jones
  /**
538
   * callback method used by the SAX Parser when the end tag of an
539
   * element is detected. Used in this context to parse and store
540
   * the query information in class variables.
541
   */
542 185 jones
  public void endElement (String uri, String localName,
543
                          String qName) throws SAXException {
544 158 jones
    BasicNode leaving = (BasicNode)elementStack.pop();
545 159 jones
    if (leaving.getTagName().equals("queryterm")) {
546
      boolean isCaseSensitive = (new Boolean(
547
              leaving.getAttribute("casesensitive"))).booleanValue();
548
      QueryTerm currentTerm = null;
549
      if (currentPathexpr == null) {
550
        currentTerm = new QueryTerm(isCaseSensitive,
551
                      leaving.getAttribute("searchmode"),currentValue);
552
      } else {
553
        currentTerm = new QueryTerm(isCaseSensitive,
554
                      leaving.getAttribute("searchmode"),currentValue,
555
                      currentPathexpr);
556
      }
557
      QueryGroup currentGroup = (QueryGroup)queryStack.peek();
558
      currentGroup.addChild(currentTerm);
559
      currentValue = null;
560
      currentPathexpr = null;
561
    } else if (leaving.getTagName().equals("querygroup")) {
562
      QueryGroup leavingGroup = (QueryGroup)queryStack.pop();
563 158 jones
    }
564 155 jones
  }
565 158 jones
566 172 jones
  /**
567
   * callback method used by the SAX Parser when the text sequences of an
568
   * xml stream are detected. Used in this context to parse and store
569
   * the query information in class variables.
570
   */
571 158 jones
  public void characters(char ch[], int start, int length) {
572
573
    String inputString = new String(ch, start, length);
574
    BasicNode currentNode = (BasicNode)elementStack.peek();
575
    String currentTag = currentNode.getTagName();
576
    if (currentTag.equals("meta_file_id")) {
577
      meta_file_id = inputString;
578 745 jones
    } else if (currentTag.equals("querytitle")) {
579
      queryTitle = inputString;
580 159 jones
    } else if (currentTag.equals("value")) {
581
      currentValue = inputString;
582
    } else if (currentTag.equals("pathexpr")) {
583
      currentPathexpr = inputString;
584 172 jones
    } else if (currentTag.equals("returndoctype")) {
585 743 jones
      returnDocList.add(inputString);
586 745 jones
    } else if (currentTag.equals("filterdoctype")) {
587
      filterDocList.add(inputString);
588 402 berkley
    } else if (currentTag.equals("returnfield")) {
589 1354 tao
      // make sure if return fields has an attribute or not
590
      if (inputString.indexOf(ATTRIBUTESYMBOL) ==-1)
591
      {
592
        // no attribute value will be returned
593
        returnFieldList.add(inputString);
594
        containsExtendedSQL = true;
595
      }
596
      else
597
      {
598
        // has a attribute return field
599
        // divied the return filed into two parts, one is path and the
600
        // other is attribue name
601
        String returnPath = newPathExpressionWithOutAttribute(inputString);
602
        String attributeName = getAttributeName(inputString);
603
        Vector pathInfo = new Vector();
604
        // the vector has the information about return path and attributename
605
        pathInfo.addElement(returnPath);
606
        pathInfo.addElement(attributeName);
607
        // put the vector into a hash table. The reseaon why don't put
608
        // return path or attributename as a key is because they are not unique
609
        attributeReturnList.put
610
                            (new Integer(countAttributeReturnField), pathInfo);
611
        countAttributeReturnField++;
612
        hasAttributeReturnField = true;
613
        containsExtendedSQL = true;
614
615
      }
616 743 jones
    } else if (currentTag.equals("filterdoctype")) {
617
      filterDocList.add(inputString);
618 535 jones
    } else if (currentTag.equals("owner")) {
619
      ownerList.add(inputString);
620
    } else if (currentTag.equals("site")) {
621
      siteList.add(inputString);
622 158 jones
    }
623
  }
624
625 172 jones
  /**
626
   * create a SQL serialization of the query that this instance represents
627
   */
628 706 bojilova
  public String printSQL(boolean useXMLIndex) {
629 1350 tao
630
631 170 jones
    StringBuffer self = new StringBuffer();
632
633 692 bojilova
    self.append("SELECT docid,docname,doctype,");
634 626 berkley
    self.append("date_created, date_updated, rev ");
635 172 jones
    self.append("FROM xml_documents WHERE docid IN (");
636 170 jones
637 178 jones
    // This determines the documents that meet the query conditions
638 706 bojilova
    self.append(query.printSQL(useXMLIndex));
639 172 jones
640
    self.append(") ");
641 402 berkley
642 172 jones
    // Add SQL to filter for doctypes requested in the query
643 743 jones
    // This is an implicit OR for the list of doctypes. Only doctypes in this
644
    // list will be searched if the tag is present
645
    if (!filterDocList.isEmpty()) {
646 172 jones
      boolean firstdoctype = true;
647
      self.append(" AND (");
648 743 jones
      Enumeration en = filterDocList.elements();
649 172 jones
      while (en.hasMoreElements()) {
650
        String currentDoctype = (String)en.nextElement();
651
        if (firstdoctype) {
652 402 berkley
           firstdoctype = false;
653
           self.append(" doctype = '" + currentDoctype + "'");
654 172 jones
        } else {
655
          self.append(" OR doctype = '" + currentDoctype + "'");
656
        }
657
      }
658
      self.append(") ");
659
    }
660 743 jones
661 535 jones
    // Add SQL to filter for owners requested in the query
662
    // This is an implicit OR for the list of owners
663
    if (!ownerList.isEmpty()) {
664
      boolean first = true;
665
      self.append(" AND (");
666
      Enumeration en = ownerList.elements();
667
      while (en.hasMoreElements()) {
668
        String current = (String)en.nextElement();
669
        if (first) {
670
           first = false;
671
           self.append(" user_owner = '" + current + "'");
672
        } else {
673
          self.append(" OR user_owner = '" + current + "'");
674
        }
675
      }
676
      self.append(") ");
677
    }
678
679
    // Add SQL to filter for sites requested in the query
680
    // This is an implicit OR for the list of sites
681
    if (!siteList.isEmpty()) {
682
      boolean first = true;
683
      self.append(" AND (");
684
      Enumeration en = siteList.elements();
685
      while (en.hasMoreElements()) {
686
        String current = (String)en.nextElement();
687
        if (first) {
688
           first = false;
689
           self.append(" SUBSTR(docid, 1, INSTR(docid, '" +
690
               accNumberSeparator + "')-1) = '" + current + "'");
691
        } else {
692
          self.append(" OR SUBSTR(docid, 1, INSTR(docid, '" +
693
               accNumberSeparator + "')-1) = '" + current + "'");
694
        }
695
      }
696
      self.append(") ");
697
    }
698 1350 tao
699
    // if there is only one percentage search item, this query is a percentage
700
    // search query
701
    if (countPercentageSearchItem ==1)
702
    {
703
704
      percentageSearch =true;
705
    }
706
707 170 jones
    return self.toString();
708
  }
709 402 berkley
710
  /**
711
   * This method prints sql based upon the &lt;returnfield&gt; tag in the
712
   * pathquery document.  This allows for customization of the
713
   * returned fields
714 465 berkley
   * @param doclist the list of document ids to search by
715 402 berkley
   */
716 465 berkley
  public String printExtendedSQL(String doclist)
717 402 berkley
  {
718
    StringBuffer self = new StringBuffer();
719 1362 tao
    self.append("select xml_nodes.docid, xml_index.path, xml_nodes.nodedata, ");
720
    self.append("xml_nodes.parentnodeid ");
721 423 berkley
    self.append("from xml_index, xml_nodes where xml_index.nodeid=");
722
    self.append("xml_nodes.parentnodeid and (xml_index.path like '");
723 402 berkley
    boolean firstfield = true;
724
    //put the returnfields into the query
725
    //the for loop allows for multiple fields
726
    for(int i=0; i<returnFieldList.size(); i++)
727
    {
728
      if(firstfield)
729
      {
730
        firstfield = false;
731 405 berkley
        self.append((String)returnFieldList.elementAt(i));
732 402 berkley
        self.append("' ");
733
      }
734
      else
735
      {
736 423 berkley
        self.append("or xml_index.path like '");
737 405 berkley
        self.append((String)returnFieldList.elementAt(i));
738 402 berkley
        self.append("' ");
739
      }
740
    }
741 423 berkley
    self.append(") AND xml_nodes.docid in (");
742 465 berkley
    //self.append(query.printSQL());
743
    self.append(doclist);
744 402 berkley
    self.append(")");
745 423 berkley
    self.append(" AND xml_nodes.nodetype = 'TEXT'");
746 405 berkley
747 1350 tao
748 402 berkley
    return self.toString();
749
  }
750 465 berkley
751 1354 tao
  /**
752
   * This method prints sql based upon the returnfield tag in the
753
   * pathquery document has an attribute.  This allows for customization of the
754
   * returned fields
755
   * @param doclist the list of document ids to search by
756
   */
757
  public String printAttributeQuery(String doclist)
758
  {
759
    StringBuffer self = new StringBuffer();
760
    self.append("select xml_nodes.docid, xml_index.path, ");
761
    self.append("xml_nodes.nodedata, xml_nodes.nodename ");
762
    self.append("from xml_index, xml_nodes where xml_index.nodeid=");
763
    self.append("xml_nodes.parentnodeid and (");
764
    boolean firstfield = true;
765
    //put the returnfields attributes into the query
766
    //the for loop allows for multiple fields and attributes
767
    Enumeration returnAttributes = attributeReturnList.elements();
768
    while (returnAttributes.hasMoreElements())
769
    {
770
      Vector currentVector = (Vector)returnAttributes.nextElement();
771
      String returnPath = (String)currentVector.elementAt(0);
772
      String attributeName = (String)currentVector.elementAt(1);
773
      if(firstfield)
774
      {
775
        firstfield = false;
776
        self.append("( xml_index.path like '");
777
        self.append(returnPath);
778
        self.append("' AND xml_nodes.nodename like '");
779
        self.append(attributeName);
780
        self.append("') ");
781
      }
782
      else
783
      {
784
        self.append(" or ( xml_index.path like '");
785
        self.append(returnPath);
786
        self.append("' AND xml_nodes.nodename like '");
787
        self.append(attributeName);
788
        self.append("') ");
789
      }
790
    }
791
    self.append(") AND xml_nodes.docid in (");
792
    //self.append(query.printSQL());
793
    self.append(doclist);
794
    self.append(")");
795
    self.append(" AND xml_nodes.nodetype = 'ATTRIBUTE'");
796
    MetaCatUtil.debugMessage("Attribute query: "+self.toString(), 30);
797
798
    return self.toString();
799
  }
800
801 465 berkley
  public static String printRelationSQL(String docid)
802
  {
803
    StringBuffer self = new StringBuffer();
804 489 berkley
    self.append("select subject, relationship, object, subdoctype, ");
805
    self.append("objdoctype from xml_relation ");
806 743 jones
    self.append("where docid like '").append(docid).append("'");
807 465 berkley
    return self.toString();
808
  }
809 453 berkley
810 172 jones
  /**
811 465 berkley
   * Prints sql that returns all relations in the database.
812 453 berkley
   */
813
  public static String printPackageSQL()
814
  {
815
    StringBuffer self = new StringBuffer();
816
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
817
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
818 743 jones
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
819 453 berkley
    self.append("from xml_index where path like ");
820 743 jones
    self.append("'triple/relationship') rel, ");
821 453 berkley
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
822 743 jones
    self.append("'triple/object') o, ");
823 453 berkley
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
824
    self.append("where s.parentnodeid = rel.parentnodeid ");
825
    self.append("and rel.parentnodeid = o.parentnodeid ");
826 899 berkley
    self.append("and x.parentnodeid in (rel.nodeid) ");
827
    self.append("and y.parentnodeid in (o.nodeid) ");
828
    self.append("and z.parentnodeid in (s.nodeid) ");
829 453 berkley
    //self.append("and z.nodedata like '%");
830
    //self.append(docid);
831
    //self.append("%'");
832
    return self.toString();
833
  }
834
835
  /**
836 465 berkley
   * Prints sql that returns all relations in the database that were input
837
   * under a specific docid
838
   * @param docid the docid to search for.
839
   */
840
  public static String printPackageSQL(String docid)
841
  {
842
    StringBuffer self = new StringBuffer();
843
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
844
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
845 743 jones
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
846 465 berkley
    self.append("from xml_index where path like ");
847 743 jones
    self.append("'triple/relationship') rel, ");
848 465 berkley
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
849 743 jones
    self.append("'triple/object') o, ");
850 465 berkley
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
851
    self.append("where s.parentnodeid = rel.parentnodeid ");
852
    self.append("and rel.parentnodeid = o.parentnodeid ");
853 899 berkley
    self.append("and x.parentnodeid in (rel.nodeid) ");
854
    self.append("and y.parentnodeid in (o.nodeid) ");
855
    self.append("and z.parentnodeid in (s.nodeid) ");
856 465 berkley
    self.append("and z.docid like '").append(docid).append("'");
857
858
    return self.toString();
859
  }
860
861
  /**
862
   * Returns all of the relations that has a certain docid in the subject
863
   * or the object.
864
   *
865
   * @param docid the docid to search for
866
   */
867
  public static String printPackageSQL(String subDocidURL, String objDocidURL)
868
  {
869
    StringBuffer self = new StringBuffer();
870
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
871
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
872 743 jones
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
873 465 berkley
    self.append("from xml_index where path like ");
874 743 jones
    self.append("'triple/relationship') rel, ");
875 465 berkley
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
876 743 jones
    self.append("'triple/object') o, ");
877 465 berkley
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
878
    self.append("where s.parentnodeid = rel.parentnodeid ");
879
    self.append("and rel.parentnodeid = o.parentnodeid ");
880 899 berkley
    self.append("and x.parentnodeid in (rel.nodeid) ");
881
    self.append("and y.parentnodeid in (o.nodeid) ");
882
    self.append("and z.parentnodeid in (s.nodeid) ");
883 465 berkley
    self.append("and (z.nodedata like '");
884
    self.append(subDocidURL);
885
    self.append("' or y.nodedata like '");
886
    self.append(objDocidURL);
887
    self.append("')");
888
    return self.toString();
889
  }
890
891
  public static String printGetDocByDoctypeSQL(String docid)
892
  {
893
    StringBuffer self = new StringBuffer();
894
895 692 bojilova
    self.append("SELECT docid,docname,doctype,");
896 465 berkley
    self.append("date_created, date_updated ");
897
    self.append("FROM xml_documents WHERE docid IN (");
898
    self.append(docid).append(")");
899
    return self.toString();
900
  }
901
902
  /**
903 172 jones
   * create a String description of the query that this instance represents.
904
   * This should become a way to get the XML serialization of the query.
905
   */
906 159 jones
  public String toString() {
907 692 bojilova
    return "meta_file_id=" + meta_file_id + "\n" + query;
908
//DOCTITLE attr cleared from the db
909
//    return "meta_file_id=" + meta_file_id + "\n" +
910
//           "querytitle=" + querytitle + "\n" + query;
911 159 jones
  }
912 1354 tao
913
  /* A method to get rid of attribute part in path expression*/
914
  public static String newPathExpressionWithOutAttribute(String pathExpression)
915
  {
916
      if (pathExpression == null)
917
      {
918
        return null;
919
      }
920
      int index = pathExpression.lastIndexOf(ATTRIBUTESYMBOL);
921
      String newExpression = null;
922
      if (index != 1)
923
      {
924
        newExpression=pathExpression.substring(0, index-1);
925
      }
926
      MetaCatUtil.debugMessage("The path expression without attributes: )" +
927
                               newExpression, 30);
928
      return newExpression;
929
  }
930
931
  /* A method to get attribute name from path */
932
  public static String getAttributeName(String path)
933
  {
934
      if (path == null)
935
      {
936
        return null;
937
      }
938
      int index = path.lastIndexOf(ATTRIBUTESYMBOL);
939
      int size = path.length();
940
      String attributeName = null;
941
       if (index != 1)
942
      {
943
        attributeName = path.substring(index+1, size);
944
      }
945
      MetaCatUtil.debugMessage("The attirbute name from path: )" +
946
                               attributeName, 30);
947
      return attributeName;
948
  }
949 159 jones
950 158 jones
  /** a utility class that represents a group of terms in a query */
951
  private class QueryGroup {
952 178 jones
    private String operator = null;  // indicates how query terms are combined
953
    private Vector children = null;  // the list of query terms and groups
954 158 jones
955 172 jones
    /**
956
     * construct a new QueryGroup
957
     *
958 178 jones
     * @param operator the boolean conector used to connect query terms
959 172 jones
     *                    in this query group
960
     */
961 178 jones
    public QueryGroup(String operator) {
962
      this.operator = operator;
963 158 jones
      children = new Vector();
964
    }
965
966 172 jones
    /**
967
     * Add a child QueryGroup to this QueryGroup
968
     *
969
     * @param qgroup the query group to be added to the list of terms
970
     */
971 158 jones
    public void addChild(QueryGroup qgroup) {
972
      children.add((Object)qgroup);
973
    }
974
975 172 jones
    /**
976
     * Add a child QueryTerm to this QueryGroup
977
     *
978
     * @param qterm the query term to be added to the list of terms
979
     */
980 158 jones
    public void addChild(QueryTerm qterm) {
981
      children.add((Object)qterm);
982
    }
983
984 172 jones
    /**
985
     * Retrieve an Enumeration of query terms for this QueryGroup
986
     */
987 158 jones
    public Enumeration getChildren() {
988
      return children.elements();
989
    }
990 159 jones
991 172 jones
    /**
992
     * create a SQL serialization of the query that this instance represents
993
     */
994 706 bojilova
    public String printSQL(boolean useXMLIndex) {
995 170 jones
      StringBuffer self = new StringBuffer();
996
      boolean first = true;
997
998
      self.append("(");
999
1000
      Enumeration en= getChildren();
1001
      while (en.hasMoreElements()) {
1002
        Object qobject = en.nextElement();
1003
        if (first) {
1004
          first = false;
1005
        } else {
1006 178 jones
          self.append(" " + operator + " ");
1007 170 jones
        }
1008
        if (qobject instanceof QueryGroup) {
1009
          QueryGroup qg = (QueryGroup)qobject;
1010 706 bojilova
          self.append(qg.printSQL(useXMLIndex));
1011 170 jones
        } else if (qobject instanceof QueryTerm) {
1012
          QueryTerm qt = (QueryTerm)qobject;
1013 706 bojilova
          self.append(qt.printSQL(useXMLIndex));
1014 170 jones
        } else {
1015
          System.err.println("qobject wrong type: fatal error");
1016
        }
1017
      }
1018
      self.append(") \n");
1019
      return self.toString();
1020
    }
1021
1022 172 jones
    /**
1023
     * create a String description of the query that this instance represents.
1024
     * This should become a way to get the XML serialization of the query.
1025
     */
1026 159 jones
    public String toString() {
1027
      StringBuffer self = new StringBuffer();
1028
1029 178 jones
      self.append("  (Query group operator=" + operator + "\n");
1030 159 jones
      Enumeration en= getChildren();
1031
      while (en.hasMoreElements()) {
1032
        Object qobject = en.nextElement();
1033
        self.append(qobject);
1034
      }
1035
      self.append("  )\n");
1036
      return self.toString();
1037
    }
1038 158 jones
  }
1039
1040
  /** a utility class that represents a single term in a query */
1041
  private class QueryTerm {
1042
    private boolean casesensitive = false;
1043
    private String searchmode = null;
1044
    private String value = null;
1045
    private String pathexpr = null;
1046 1350 tao
    private boolean percentageSymbol = false;
1047
1048 158 jones
1049 172 jones
    /**
1050
     * Construct a new instance of a query term for a free text search
1051
     * (using the value only)
1052
     *
1053
     * @param casesensitive flag indicating whether case is used to match
1054
     * @param searchmode determines what kind of substring match is performed
1055
     *        (one of starts-with|ends-with|contains|matches-exactly)
1056
     * @param value the text value to match
1057
     */
1058 158 jones
    public QueryTerm(boolean casesensitive, String searchmode,
1059
                     String value) {
1060
      this.casesensitive = casesensitive;
1061
      this.searchmode = searchmode;
1062
      this.value = value;
1063
    }
1064
1065 172 jones
    /**
1066
     * Construct a new instance of a query term for a structured search
1067
     * (matching the value only for those nodes in the pathexpr)
1068
     *
1069
     * @param casesensitive flag indicating whether case is used to match
1070
     * @param searchmode determines what kind of substring match is performed
1071
     *        (one of starts-with|ends-with|contains|matches-exactly)
1072
     * @param value the text value to match
1073
     * @param pathexpr the hierarchical path to the nodes to be searched
1074
     */
1075 158 jones
    public QueryTerm(boolean casesensitive, String searchmode,
1076
                     String value, String pathexpr) {
1077
      this(casesensitive, searchmode, value);
1078
      this.pathexpr = pathexpr;
1079
    }
1080
1081 172 jones
    /** determine if the QueryTerm is case sensitive */
1082 158 jones
    public boolean isCaseSensitive() {
1083
      return casesensitive;
1084
    }
1085
1086 172 jones
    /** get the searchmode parameter */
1087 158 jones
    public String getSearchMode() {
1088
      return searchmode;
1089
    }
1090
1091 172 jones
    /** get the Value parameter */
1092 158 jones
    public String getValue() {
1093
      return value;
1094
    }
1095
1096 172 jones
    /** get the path expression parameter */
1097 158 jones
    public String getPathExpression() {
1098
      return pathexpr;
1099
    }
1100 159 jones
1101 172 jones
    /**
1102
     * create a SQL serialization of the query that this instance represents
1103
     */
1104 706 bojilova
    public String printSQL(boolean useXMLIndex) {
1105 170 jones
      StringBuffer self = new StringBuffer();
1106
1107
      // Uppercase the search string if case match is not important
1108
      String casevalue = null;
1109
      String nodedataterm = null;
1110
1111
      if (casesensitive) {
1112
        nodedataterm = "nodedata";
1113
        casevalue = value;
1114
      } else {
1115
        nodedataterm = "UPPER(nodedata)";
1116
        casevalue = value.toUpperCase();
1117
      }
1118
1119
      // Add appropriate wildcards to search string
1120 795 bojilova
      //String searchvalue = null;
1121
      String searchexpr = null;
1122 170 jones
      if (searchmode.equals("starts-with")) {
1123 795 bojilova
        //searchvalue = casevalue + "%";
1124
        searchexpr = nodedataterm + " LIKE '" + casevalue + "%' ";
1125 170 jones
      } else if (searchmode.equals("ends-with")) {
1126 795 bojilova
        //searchvalue = "%" + casevalue;
1127
        searchexpr = nodedataterm + " LIKE '%" + casevalue + "' ";
1128 170 jones
      } else if (searchmode.equals("contains")) {
1129 795 bojilova
        //searchvalue = "%" + casevalue + "%";
1130 1305 tao
        if (!casevalue.equals("%"))
1131
        {
1132
          searchexpr = nodedataterm + " LIKE '%" + casevalue + "%' ";
1133
        }
1134
        else
1135
        {
1136
          searchexpr = nodedataterm + " LIKE '" + casevalue + "' ";
1137 1350 tao
          // find percentage symbol
1138
          percentageSymbol = true;
1139 1305 tao
        }
1140 795 bojilova
      } else if (searchmode.equals("equals")) {
1141
        //searchvalue = casevalue;
1142
        searchexpr = nodedataterm + " = '" + casevalue + "' ";
1143
      } else if (searchmode.equals("isnot-equal")) {
1144
        //searchvalue = casevalue;
1145
        searchexpr = nodedataterm + " != '" + casevalue + "' ";
1146
      } else {
1147
        //searchvalue = casevalue;
1148
        String oper = null;
1149
        if (searchmode.equals("greater-than")) {
1150
          oper = ">";
1151
        } else if (searchmode.equals("greater-than-equals")) {
1152
          oper = ">=";
1153
        } else if (searchmode.equals("less-than")) {
1154
          oper = "<";
1155
        } else if (searchmode.equals("less-than-equals")) {
1156
          oper = "<=";
1157
        } else {
1158
          System.out.println("NOT expected case. NOT recognized operator: " +
1159
                             searchmode);
1160
          return null;
1161
        }
1162
        try {
1163
          // it is number; numeric comparison
1164 1332 tao
          // but we need to make sure there is no string in node data
1165
          String getRidOfString = " AND UPPER(nodedata) = LOWER(nodedata)" +
1166
                                  " AND LTRIM(nodedata) != ' ' " +
1167
                                  " AND nodedata IS NOT NULL ";
1168 795 bojilova
          searchexpr = nodedataterm + " " + oper + " " +
1169 1332 tao
                       new Double(casevalue) + " "+getRidOfString;
1170 795 bojilova
        } catch (NumberFormatException nfe) {
1171
          // these are characters; character comparison
1172
          searchexpr = nodedataterm + " " + oper + " '" + casevalue + "' ";
1173
        }
1174 170 jones
      }
1175
1176 178 jones
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
1177 795 bojilova
      //self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
1178
      self.append(searchexpr);
1179 170 jones
1180 1348 tao
      if (pathexpr != null)
1181
      {
1182
1183 706 bojilova
        // use XML Index
1184 1348 tao
        if ( useXMLIndex )
1185
        {
1186
          if (!hasAttributeInPath(pathexpr))
1187
          {
1188
            // without attributes in path
1189
            self.append("AND parentnodeid IN ");
1190
          }
1191
          else
1192
          {
1193
            // has a attribute in path
1194
            String attributeName = getAttributeName(pathexpr);
1195
            self.append("AND nodetype LIKE 'ATTRIBUTE' AND nodename LIKE '"+
1196
                        attributeName + "' ");
1197
            self.append("AND parentnodeid IN ");
1198
            pathexpr = newPathExpressionWithOutAttribute(pathexpr);
1199
1200
          }
1201 706 bojilova
          self.append("(SELECT nodeid FROM xml_index WHERE path LIKE " +
1202
                      "'" +  pathexpr + "') " );
1203 1348 tao
        }
1204
        else
1205
        {
1206
          // without using XML Index; using nested statements instead
1207
          self.append("AND parentnodeid IN ");
1208 706 bojilova
          self.append(useNestedStatements(pathexpr));
1209
        }
1210 170 jones
      }
1211 1350 tao
      else
1212
      {
1213
        //if pathexpr is null and search value is %, is a percentageSearchItem
1214
        // the count number will be increase one
1215
        countPercentageSearchItem++;
1216
1217
       }
1218 170 jones
1219
      return self.toString();
1220
    }
1221 1348 tao
1222
    /* A method to judge if a path have attribute */
1223
    private boolean hasAttributeInPath(String path)
1224
    {
1225
      if (path.indexOf(ATTRIBUTESYMBOL)!=-1)
1226
      {
1227
        return true;
1228
      }
1229
      else
1230
      {
1231
        return false;
1232
      }
1233
    }
1234
1235 1354 tao
1236 1348 tao
1237 706 bojilova
    /*
1238
     * Constraint the query with @pathexp without using the XML Index,
1239
     * but nested SQL statements instead. The query migth be slower.
1240
     */
1241
    private String useNestedStatements(String pathexpr)
1242
    {
1243
      StringBuffer nestedStmts = new StringBuffer();
1244
      Vector nodes = new Vector();
1245
      String path = pathexpr;
1246
      int inx = 0;
1247
1248
      do {
1249
        inx = path.lastIndexOf("/");
1250 1350 tao
1251 706 bojilova
        nodes.addElement(path.substring(inx+1));
1252
        path = path.substring(0, Math.abs(inx));
1253
      } while ( inx > 0 );
1254
1255
      // nested statements
1256
      int i = 0;
1257
      for (i = 0; i < nodes.size()-1; i++) {
1258
        nestedStmts.append("(SELECT nodeid FROM xml_nodes" +
1259
                           " WHERE nodename LIKE '" +
1260
                             (String)nodes.elementAt(i) + "'" +
1261
                           " AND parentnodeid IN ");
1262
      }
1263
      // for the last statement: it is without " AND parentnodeid IN "
1264
      nestedStmts.append("(SELECT nodeid FROM xml_nodes" +
1265
                         " WHERE nodename LIKE '" +
1266
                         (String)nodes.elementAt(i) + "'" );
1267
      // node.size() number of closing brackets
1268
      for (i = 0; i < nodes.size(); i++) {
1269
        nestedStmts.append(")");
1270
      }
1271
1272
1273 1350 tao
1274 706 bojilova
      return nestedStmts.toString();
1275
    }
1276
1277 172 jones
    /**
1278
     * create a String description of the query that this instance represents.
1279
     * This should become a way to get the XML serialization of the query.
1280
     */
1281 709 bojilova
    public String toString() {
1282 159 jones
1283 709 bojilova
      return this.printSQL(true);
1284 159 jones
    }
1285 158 jones
  }
1286 155 jones
}