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: tao $'
13
 *     '$Date: 2003-01-09 16:29:05 -0800 (Thu, 09 Jan 2003) $'
14
 * '$Revision: 1362 $'
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.Hashtable;
37
import java.util.Stack;
38
import java.util.Vector;
39
import java.util.Enumeration;
40

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

    
49
/**
50
 * 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
 */
54
public class QuerySpecification extends DefaultHandler {
55
 
56
  /** flag determining whether extended query terms are present */
57
  private boolean containsExtendedSQL=false;
58
  /** Identifier for this query document */
59
  private String meta_file_id;
60
  /** Title of this query */
61
  private String queryTitle;
62
  /** List of document types to be returned using package back tracing */
63
  private Vector returnDocList;
64
  /** List of document types to be searched */
65
  private Vector filterDocList;
66
  /** List of fields to be returned in result set */
67
  private Vector returnFieldList;
68
  /** List of users owning documents to be searched */
69
  private Vector ownerList;
70
  /** List of sites/scopes used to constrain search */
71
  private Vector siteList;
72
  /** The root query group that contains the recursive query constraints */
73
  private QueryGroup query = null;
74
 
75
  // Query data structures used temporarily during XML parsing
76
  private Stack elementStack;
77
  private Stack queryStack;
78
  private String currentValue;
79
  private String currentPathexpr;
80
  private String parserName = null;
81
  private String accNumberSeparator = null;
82
  private static final AbstractDatabase dbAdapter = MetaCatUtil.dbAdapter;
83
  
84
  private int countPercentageSearchItem = 0;
85
  private boolean percentageSearch = false;
86
  
87
  private String userName = null;
88
  private static final String PUBLIC = "public";
89
  private String [] group = null;
90

    
91
  public static final String ATTRIBUTESYMBOL = "@";
92
  private boolean hasAttributeReturnField = false;
93
  private Hashtable attributeReturnList = new Hashtable();
94
  private int countAttributeReturnField = 0;
95
  /**
96
   * construct an instance of the QuerySpecification class 
97
   *
98
   * @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
   *                  the org.xml.sax.XMLReader interface
102
   */
103
  public QuerySpecification( Reader queryspec, String parserName,
104
         String accNumberSeparator ) throws IOException {
105
    super();
106
    
107
    // Initialize the class variables
108
    returnDocList = new Vector();
109
    filterDocList = new Vector();
110
    elementStack = new Stack();
111
    queryStack   = new Stack();
112
    returnFieldList = new Vector();
113
    ownerList = new Vector();
114
    siteList = new Vector();
115
    this.parserName = parserName;
116
    this.accNumberSeparator = accNumberSeparator;
117

    
118
    // Initialize the parser and read the queryspec
119
    XMLReader parser = initializeParser();
120
    if (parser == null) {
121
      System.err.println("SAX parser not instantiated properly.");
122
    }
123
    try {
124
      parser.parse(new InputSource(queryspec));
125
    } catch (SAXException e) {
126
      System.err.println("error parsing data in " + 
127
                         "QuerySpecification.QuerySpecification");
128
      System.err.println(e.getMessage());
129
    }
130
  }
131

    
132
  /**
133
   * construct an instance of the QuerySpecification class 
134
   *
135
   * @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
   */
140
  public QuerySpecification( String queryspec, String parserName,
141
         String accNumberSeparator) throws IOException {
142
    this(new StringReader(queryspec), parserName, accNumberSeparator);
143
  }
144
  
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
  /**
165
   * Method to indicate this query is a percentage search
166
   */
167
  public boolean isPercentageSearch()
168
  {
169
    return percentageSearch;
170
  }
171
  /*
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
                              +"' AND perm_type = 'allow'"
212
                              +" AND (permission='4' OR permission='7'))";
213
    }
214
    // add allow rule for public
215
    allowQuery = allowQuery +"OR (principal_name = '" + PUBLIC 
216
                              +"' AND perm_type = 'allow'"
217
                              +" AND (permission='4' OR permission='7'))";
218
    
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
                              +"' AND perm_type = 'allow'"
229
                              +" AND (permission='4' OR permission='7'))";
230
        }//if
231
      }//for
232
    }//if
233
    MetaCatUtil.debugMessage("allow query is: "+ allowQuery, 30);
234
    return allowQuery;
235
  
236
  }
237

    
238
   /*
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
                              +"AND perm_order ='allowFirst'"
253
                              +" AND (permission='4' OR permission='7'))";
254
    }
255
    // add deny rule for public
256
    denyQuery = denyQuery +"OR (principal_name = '" + PUBLIC 
257
                               +"' AND perm_type = 'deny' "
258
                               +"AND perm_order ='allowFirst'"
259
                               +" AND (permission='4' OR permission='7'))";
260
    
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
                                +"AND perm_order ='allowFirst'"
272
                                +" AND (permission='4' OR permission='7'))";
273
        }//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
  /** 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
       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
       try {
318
         MetaCatUtil util = new MetaCatUtil();
319
         FileReader xml = new FileReader(new File(xmlfile));
320
         QuerySpecification qspec = 
321
                 new QuerySpecification(xml, util.getOption("saxparser"),
322
                                        util.getOption("accNumberSeparator"));
323
         System.out.println(qspec.printSQL(useXMLIndex));
324

    
325
       } catch (IOException e) {
326
         System.err.println(e.getMessage());
327
       }
328
         
329
     }
330
  }
331
  
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
   * 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
   * 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
   * 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

    
430
  /**
431
   * 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
   * Set up the SAX parser for reading the XML serialized query
482
   */
483
  private XMLReader initializeParser() {
484
    XMLReader parser = null;
485

    
486
    // Set up the SAX document handlers for parsing
487
    try {
488

    
489
      // Get an instance of the parser
490
      parser = XMLReaderFactory.createXMLReader(parserName);
491

    
492
      // Set the ContentHandler to this instance
493
      parser.setContentHandler(this);
494

    
495
      // Set the error Handler to this instance
496
      parser.setErrorHandler(this);
497

    
498
    } catch (Exception e) {
499
       System.err.println("Error in QuerySpcecification.initializeParser " + 
500
                           e.toString());
501
    }
502

    
503
    return parser;
504
  }
505

    
506
  /**
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
  public void startElement (String uri, String localName, 
512
                            String qName, Attributes atts) 
513
         throws SAXException {
514
    BasicNode currentNode = new BasicNode(localName);
515
    // add attributes to BasicNode here
516
    if (atts != null) {
517
      int len = atts.getLength();
518
      for (int i = 0; i < len; i++) {
519
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
520
      }
521
    }
522

    
523
    elementStack.push(currentNode); 
524
    if (currentNode.getTagName().equals("querygroup")) {
525
      QueryGroup currentGroup = new QueryGroup(
526
                                currentNode.getAttribute("operator"));
527
      if (query == null) {
528
        query = currentGroup;
529
      } else {
530
        QueryGroup parentGroup = (QueryGroup)queryStack.peek();
531
        parentGroup.addChild(currentGroup);
532
      }
533
      queryStack.push(currentGroup);
534
    }
535
  }
536

    
537
  /**
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
  public void endElement (String uri, String localName,
543
                          String qName) throws SAXException {
544
    BasicNode leaving = (BasicNode)elementStack.pop(); 
545
    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
    }
564
  }
565

    
566
  /**
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
  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
    } else if (currentTag.equals("querytitle")) {
579
      queryTitle = inputString;
580
    } else if (currentTag.equals("value")) {
581
      currentValue = inputString;
582
    } else if (currentTag.equals("pathexpr")) {
583
      currentPathexpr = inputString;
584
    } else if (currentTag.equals("returndoctype")) {
585
      returnDocList.add(inputString);
586
    } else if (currentTag.equals("filterdoctype")) {
587
      filterDocList.add(inputString);
588
    } else if (currentTag.equals("returnfield")) {
589
      // 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
    } else if (currentTag.equals("filterdoctype")) {
617
      filterDocList.add(inputString);
618
    } else if (currentTag.equals("owner")) {
619
      ownerList.add(inputString);
620
    } else if (currentTag.equals("site")) {
621
      siteList.add(inputString);
622
    }
623
  }
624

    
625
  /**
626
   * create a SQL serialization of the query that this instance represents
627
   */
628
  public String printSQL(boolean useXMLIndex) {
629
    
630
   
631
    StringBuffer self = new StringBuffer();
632

    
633
    self.append("SELECT docid,docname,doctype,");
634
    self.append("date_created, date_updated, rev ");
635
    self.append("FROM xml_documents WHERE docid IN (");
636

    
637
    // This determines the documents that meet the query conditions
638
    self.append(query.printSQL(useXMLIndex));
639

    
640
    self.append(") ");
641
 
642
    // Add SQL to filter for doctypes requested in the query
643
    // 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
      boolean firstdoctype = true;
647
      self.append(" AND ("); 
648
      Enumeration en = filterDocList.elements();
649
      while (en.hasMoreElements()) {
650
        String currentDoctype = (String)en.nextElement();
651
        if (firstdoctype) {
652
           firstdoctype = false;
653
           self.append(" doctype = '" + currentDoctype + "'"); 
654
        } else {
655
          self.append(" OR doctype = '" + currentDoctype + "'"); 
656
        }
657
      }
658
      self.append(") ");
659
    }
660

    
661
    // 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
    
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
    return self.toString();
708
  }
709
  
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
   * @param doclist the list of document ids to search by
715
   */
716
  public String printExtendedSQL(String doclist)
717
  {  
718
    StringBuffer self = new StringBuffer();
719
    self.append("select xml_nodes.docid, xml_index.path, xml_nodes.nodedata, ");
720
    self.append("xml_nodes.parentnodeid ");
721
    self.append("from xml_index, xml_nodes where xml_index.nodeid=");
722
    self.append("xml_nodes.parentnodeid and (xml_index.path like '");
723
    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
        self.append((String)returnFieldList.elementAt(i));
732
        self.append("' ");
733
      }
734
      else
735
      {
736
        self.append("or xml_index.path like '");
737
        self.append((String)returnFieldList.elementAt(i));
738
        self.append("' ");
739
      }
740
    }
741
    self.append(") AND xml_nodes.docid in (");
742
    //self.append(query.printSQL());
743
    self.append(doclist);
744
    self.append(")");
745
    self.append(" AND xml_nodes.nodetype = 'TEXT'");
746

    
747
   
748
    return self.toString();
749
  }
750
  
751
  /**
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
  public static String printRelationSQL(String docid)
802
  {
803
    StringBuffer self = new StringBuffer();
804
    self.append("select subject, relationship, object, subdoctype, ");
805
    self.append("objdoctype from xml_relation ");
806
    self.append("where docid like '").append(docid).append("'");
807
    return self.toString();
808
  }
809
   
810
  /**
811
   * Prints sql that returns all relations in the database.
812
   */
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
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
819
    self.append("from xml_index where path like ");
820
    self.append("'triple/relationship') rel, ");
821
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
822
    self.append("'triple/object') o, ");
823
    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
    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
    //self.append("and z.nodedata like '%");
830
    //self.append(docid);
831
    //self.append("%'");
832
    return self.toString();
833
  }
834
  
835
  /**
836
   * 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
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
846
    self.append("from xml_index where path like ");
847
    self.append("'triple/relationship') rel, ");
848
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
849
    self.append("'triple/object') o, ");
850
    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
    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
    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
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
873
    self.append("from xml_index where path like ");
874
    self.append("'triple/relationship') rel, ");
875
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
876
    self.append("'triple/object') o, ");
877
    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
    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
    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
    self.append("SELECT docid,docname,doctype,");
896
    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
   * 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
  public String toString() {
907
    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
  }
912
  
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

    
950
  /** a utility class that represents a group of terms in a query */
951
  private class QueryGroup {
952
    private String operator = null;  // indicates how query terms are combined
953
    private Vector children = null;  // the list of query terms and groups
954

    
955
    /** 
956
     * construct a new QueryGroup 
957
     *
958
     * @param operator the boolean conector used to connect query terms 
959
     *                    in this query group
960
     */
961
    public QueryGroup(String operator) {
962
      this.operator = operator;
963
      children = new Vector();
964
    }
965

    
966
    /** 
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
    public void addChild(QueryGroup qgroup) {
972
      children.add((Object)qgroup); 
973
    }
974

    
975
    /**
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
    public void addChild(QueryTerm qterm) {
981
      children.add((Object)qterm); 
982
    }
983

    
984
    /**
985
     * Retrieve an Enumeration of query terms for this QueryGroup
986
     */
987
    public Enumeration getChildren() {
988
      return children.elements();
989
    }
990
   
991
    /**
992
     * create a SQL serialization of the query that this instance represents
993
     */
994
    public String printSQL(boolean useXMLIndex) {
995
      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
          self.append(" " + operator + " ");
1007
        }
1008
        if (qobject instanceof QueryGroup) {
1009
          QueryGroup qg = (QueryGroup)qobject;
1010
          self.append(qg.printSQL(useXMLIndex));
1011
        } else if (qobject instanceof QueryTerm) {
1012
          QueryTerm qt = (QueryTerm)qobject;
1013
          self.append(qt.printSQL(useXMLIndex));
1014
        } else {
1015
          System.err.println("qobject wrong type: fatal error");
1016
        }
1017
      }
1018
      self.append(") \n");
1019
      return self.toString();
1020
    }
1021

    
1022
    /**
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
    public String toString() {
1027
      StringBuffer self = new StringBuffer();
1028

    
1029
      self.append("  (Query group operator=" + operator + "\n");
1030
      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
  }
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
    private boolean percentageSymbol = false;
1047
   
1048

    
1049
    /**
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
    public QueryTerm(boolean casesensitive, String searchmode, 
1059
                     String value) {
1060
      this.casesensitive = casesensitive;
1061
      this.searchmode = searchmode;
1062
      this.value = value;
1063
    }
1064

    
1065
    /**
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
    public QueryTerm(boolean casesensitive, String searchmode, 
1076
                     String value, String pathexpr) {
1077
      this(casesensitive, searchmode, value);
1078
      this.pathexpr = pathexpr;
1079
    }
1080

    
1081
    /** determine if the QueryTerm is case sensitive */
1082
    public boolean isCaseSensitive() {
1083
      return casesensitive;
1084
    }
1085

    
1086
    /** get the searchmode parameter */
1087
    public String getSearchMode() {
1088
      return searchmode;
1089
    }
1090
 
1091
    /** get the Value parameter */
1092
    public String getValue() {
1093
      return value;
1094
    }
1095

    
1096
    /** get the path expression parameter */
1097
    public String getPathExpression() {
1098
      return pathexpr;
1099
    }
1100

    
1101
    /**
1102
     * create a SQL serialization of the query that this instance represents
1103
     */
1104
    public String printSQL(boolean useXMLIndex) {
1105
      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
      //String searchvalue = null;
1121
      String searchexpr = null;
1122
      if (searchmode.equals("starts-with")) {
1123
        //searchvalue = casevalue + "%";
1124
        searchexpr = nodedataterm + " LIKE '" + casevalue + "%' ";
1125
      } else if (searchmode.equals("ends-with")) {
1126
        //searchvalue = "%" + casevalue;
1127
        searchexpr = nodedataterm + " LIKE '%" + casevalue + "' ";
1128
      } else if (searchmode.equals("contains")) {
1129
        //searchvalue = "%" + casevalue + "%";
1130
        if (!casevalue.equals("%"))
1131
        {
1132
          searchexpr = nodedataterm + " LIKE '%" + casevalue + "%' ";
1133
        }
1134
        else
1135
        {
1136
          searchexpr = nodedataterm + " LIKE '" + casevalue + "' ";
1137
          // find percentage symbol
1138
          percentageSymbol = true;
1139
        }
1140
      } 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
          // 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
          searchexpr = nodedataterm + " " + oper + " " +
1169
                       new Double(casevalue) + " "+getRidOfString;          
1170
        } catch (NumberFormatException nfe) {
1171
          // these are characters; character comparison
1172
          searchexpr = nodedataterm + " " + oper + " '" + casevalue + "' ";
1173
        }
1174
      }
1175

    
1176
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
1177
      //self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
1178
      self.append(searchexpr);
1179

    
1180
      if (pathexpr != null) 
1181
      {
1182
        
1183
        // use XML Index
1184
        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
          self.append("(SELECT nodeid FROM xml_index WHERE path LIKE " + 
1202
                      "'" +  pathexpr + "') " );
1203
        } 
1204
        else 
1205
        {
1206
          // without using XML Index; using nested statements instead
1207
          self.append("AND parentnodeid IN ");
1208
          self.append(useNestedStatements(pathexpr));
1209
        }
1210
      }
1211
      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

    
1219
      return self.toString();
1220
    }
1221
    
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
   
1236
    
1237
    /* 
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

    
1251
        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

    
1274
      return nestedStmts.toString();
1275
    }
1276

    
1277
    /**
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
    public String toString() {
1282

    
1283
      return this.printSQL(true);
1284
    }
1285
  }
1286
}
(40-40/47)