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-07 17:03:42 -0800 (Tue, 07 Jan 2003) $'
14
 * '$Revision: 1354 $'
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("from xml_index, xml_nodes where xml_index.nodeid=");
721
    self.append("xml_nodes.parentnodeid and (xml_index.path like '");
722
    boolean firstfield = true;
723
    //put the returnfields into the query
724
    //the for loop allows for multiple fields
725
    for(int i=0; i<returnFieldList.size(); i++)
726
    {
727
      if(firstfield)
728
      {
729
        firstfield = false;
730
        self.append((String)returnFieldList.elementAt(i));
731
        self.append("' ");
732
      }
733
      else
734
      {
735
        self.append("or xml_index.path like '");
736
        self.append((String)returnFieldList.elementAt(i));
737
        self.append("' ");
738
      }
739
    }
740
    self.append(") AND xml_nodes.docid in (");
741
    //self.append(query.printSQL());
742
    self.append(doclist);
743
    self.append(")");
744
    self.append(" AND xml_nodes.nodetype = 'TEXT'");
745

    
746
   
747
    return self.toString();
748
  }
749
  
750
  /**
751
   * This method prints sql based upon the returnfield tag in the
752
   * pathquery document has an attribute.  This allows for customization of the 
753
   * returned fields
754
   * @param doclist the list of document ids to search by
755
   */
756
  public String printAttributeQuery(String doclist)
757
  {
758
    StringBuffer self = new StringBuffer();
759
    self.append("select xml_nodes.docid, xml_index.path, ");
760
    self.append("xml_nodes.nodedata, xml_nodes.nodename ");
761
    self.append("from xml_index, xml_nodes where xml_index.nodeid=");
762
    self.append("xml_nodes.parentnodeid and (");
763
    boolean firstfield = true;
764
    //put the returnfields attributes into the query
765
    //the for loop allows for multiple fields and attributes
766
    Enumeration returnAttributes = attributeReturnList.elements();
767
    while (returnAttributes.hasMoreElements())
768
    {
769
      Vector currentVector = (Vector)returnAttributes.nextElement();
770
      String returnPath = (String)currentVector.elementAt(0);
771
      String attributeName = (String)currentVector.elementAt(1);
772
      if(firstfield)
773
      {
774
        firstfield = false;
775
        self.append("( xml_index.path like '");
776
        self.append(returnPath);
777
        self.append("' AND xml_nodes.nodename like '");
778
        self.append(attributeName);
779
        self.append("') ");
780
      }
781
      else
782
      {
783
        self.append(" or ( xml_index.path like '");
784
        self.append(returnPath);
785
        self.append("' AND xml_nodes.nodename like '");
786
        self.append(attributeName);
787
        self.append("') "); 
788
      }
789
    }
790
    self.append(") AND xml_nodes.docid in (");
791
    //self.append(query.printSQL());
792
    self.append(doclist);
793
    self.append(")");
794
    self.append(" AND xml_nodes.nodetype = 'ATTRIBUTE'");
795
    MetaCatUtil.debugMessage("Attribute query: "+self.toString(), 30);
796
   
797
    return self.toString();
798
  }
799
  
800
  public static String printRelationSQL(String docid)
801
  {
802
    StringBuffer self = new StringBuffer();
803
    self.append("select subject, relationship, object, subdoctype, ");
804
    self.append("objdoctype from xml_relation ");
805
    self.append("where docid like '").append(docid).append("'");
806
    return self.toString();
807
  }
808
   
809
  /**
810
   * Prints sql that returns all relations in the database.
811
   */
812
  public static String printPackageSQL()
813
  {
814
    StringBuffer self = new StringBuffer();
815
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
816
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
817
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
818
    self.append("from xml_index where path like ");
819
    self.append("'triple/relationship') rel, ");
820
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
821
    self.append("'triple/object') o, ");
822
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
823
    self.append("where s.parentnodeid = rel.parentnodeid ");
824
    self.append("and rel.parentnodeid = o.parentnodeid ");
825
    self.append("and x.parentnodeid in (rel.nodeid) ");
826
    self.append("and y.parentnodeid in (o.nodeid) ");
827
    self.append("and z.parentnodeid in (s.nodeid) ");
828
    //self.append("and z.nodedata like '%");
829
    //self.append(docid);
830
    //self.append("%'");
831
    return self.toString();
832
  }
833
  
834
  /**
835
   * Prints sql that returns all relations in the database that were input
836
   * under a specific docid
837
   * @param docid the docid to search for.
838
   */
839
  public static String printPackageSQL(String docid)
840
  {
841
    StringBuffer self = new StringBuffer();
842
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
843
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
844
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
845
    self.append("from xml_index where path like ");
846
    self.append("'triple/relationship') rel, ");
847
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
848
    self.append("'triple/object') o, ");
849
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
850
    self.append("where s.parentnodeid = rel.parentnodeid ");
851
    self.append("and rel.parentnodeid = o.parentnodeid ");
852
    self.append("and x.parentnodeid in (rel.nodeid) ");
853
    self.append("and y.parentnodeid in (o.nodeid) ");
854
    self.append("and z.parentnodeid in (s.nodeid) ");
855
    self.append("and z.docid like '").append(docid).append("'");
856
    
857
    return self.toString();
858
  }
859
  
860
  /**
861
   * Returns all of the relations that has a certain docid in the subject
862
   * or the object.
863
   * 
864
   * @param docid the docid to search for
865
   */
866
  public static String printPackageSQL(String subDocidURL, String objDocidURL)
867
  {
868
    StringBuffer self = new StringBuffer();
869
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
870
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
871
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
872
    self.append("from xml_index where path like ");
873
    self.append("'triple/relationship') rel, ");
874
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
875
    self.append("'triple/object') o, ");
876
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
877
    self.append("where s.parentnodeid = rel.parentnodeid ");
878
    self.append("and rel.parentnodeid = o.parentnodeid ");
879
    self.append("and x.parentnodeid in (rel.nodeid) ");
880
    self.append("and y.parentnodeid in (o.nodeid) ");
881
    self.append("and z.parentnodeid in (s.nodeid) ");
882
    self.append("and (z.nodedata like '");
883
    self.append(subDocidURL);
884
    self.append("' or y.nodedata like '");
885
    self.append(objDocidURL);
886
    self.append("')");
887
    return self.toString();
888
  }
889
  
890
  public static String printGetDocByDoctypeSQL(String docid)
891
  {
892
    StringBuffer self = new StringBuffer();
893

    
894
    self.append("SELECT docid,docname,doctype,");
895
    self.append("date_created, date_updated ");
896
    self.append("FROM xml_documents WHERE docid IN (");
897
    self.append(docid).append(")");
898
    return self.toString();
899
  }
900
  
901
  /**
902
   * create a String description of the query that this instance represents.
903
   * This should become a way to get the XML serialization of the query.
904
   */
905
  public String toString() {
906
    return "meta_file_id=" + meta_file_id + "\n" + query;
907
//DOCTITLE attr cleared from the db
908
//    return "meta_file_id=" + meta_file_id + "\n" + 
909
//           "querytitle=" + querytitle + "\n" + query;
910
  }
911
  
912
  /* A method to get rid of attribute part in path expression*/
913
  public static String newPathExpressionWithOutAttribute(String pathExpression)
914
  {
915
      if (pathExpression == null)
916
      {
917
        return null;
918
      }
919
      int index = pathExpression.lastIndexOf(ATTRIBUTESYMBOL);
920
      String newExpression = null;
921
      if (index != 1)
922
      {
923
        newExpression=pathExpression.substring(0, index-1);
924
      } 
925
      MetaCatUtil.debugMessage("The path expression without attributes: )" + 
926
                               newExpression, 30);
927
      return newExpression;
928
  }
929
    
930
  /* A method to get attribute name from path */
931
  public static String getAttributeName(String path)
932
  {
933
      if (path == null)
934
      {
935
        return null;
936
      }
937
      int index = path.lastIndexOf(ATTRIBUTESYMBOL);
938
      int size = path.length();
939
      String attributeName = null;
940
       if (index != 1)
941
      {
942
        attributeName = path.substring(index+1, size);
943
      } 
944
      MetaCatUtil.debugMessage("The attirbute name from path: )" + 
945
                               attributeName, 30);
946
      return attributeName;
947
  }
948

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

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

    
965
    /** 
966
     * Add a child QueryGroup to this QueryGroup
967
     *
968
     * @param qgroup the query group to be added to the list of terms
969
     */
970
    public void addChild(QueryGroup qgroup) {
971
      children.add((Object)qgroup); 
972
    }
973

    
974
    /**
975
     * Add a child QueryTerm to this QueryGroup
976
     *
977
     * @param qterm the query term to be added to the list of terms
978
     */
979
    public void addChild(QueryTerm qterm) {
980
      children.add((Object)qterm); 
981
    }
982

    
983
    /**
984
     * Retrieve an Enumeration of query terms for this QueryGroup
985
     */
986
    public Enumeration getChildren() {
987
      return children.elements();
988
    }
989
   
990
    /**
991
     * create a SQL serialization of the query that this instance represents
992
     */
993
    public String printSQL(boolean useXMLIndex) {
994
      StringBuffer self = new StringBuffer();
995
      boolean first = true;
996

    
997
      self.append("(");
998

    
999
      Enumeration en= getChildren();
1000
      while (en.hasMoreElements()) {
1001
        Object qobject = en.nextElement();
1002
        if (first) {
1003
          first = false;
1004
        } else {
1005
          self.append(" " + operator + " ");
1006
        }
1007
        if (qobject instanceof QueryGroup) {
1008
          QueryGroup qg = (QueryGroup)qobject;
1009
          self.append(qg.printSQL(useXMLIndex));
1010
        } else if (qobject instanceof QueryTerm) {
1011
          QueryTerm qt = (QueryTerm)qobject;
1012
          self.append(qt.printSQL(useXMLIndex));
1013
        } else {
1014
          System.err.println("qobject wrong type: fatal error");
1015
        }
1016
      }
1017
      self.append(") \n");
1018
      return self.toString();
1019
    }
1020

    
1021
    /**
1022
     * create a String description of the query that this instance represents.
1023
     * This should become a way to get the XML serialization of the query.
1024
     */
1025
    public String toString() {
1026
      StringBuffer self = new StringBuffer();
1027

    
1028
      self.append("  (Query group operator=" + operator + "\n");
1029
      Enumeration en= getChildren();
1030
      while (en.hasMoreElements()) {
1031
        Object qobject = en.nextElement();
1032
        self.append(qobject);
1033
      }
1034
      self.append("  )\n");
1035
      return self.toString();
1036
    }
1037
  }
1038

    
1039
  /** a utility class that represents a single term in a query */
1040
  private class QueryTerm {
1041
    private boolean casesensitive = false;
1042
    private String searchmode = null;
1043
    private String value = null;
1044
    private String pathexpr = null;
1045
    private boolean percentageSymbol = false;
1046
   
1047

    
1048
    /**
1049
     * Construct a new instance of a query term for a free text search
1050
     * (using the value only)
1051
     *
1052
     * @param casesensitive flag indicating whether case is used to match
1053
     * @param searchmode determines what kind of substring match is performed
1054
     *        (one of starts-with|ends-with|contains|matches-exactly)
1055
     * @param value the text value to match
1056
     */
1057
    public QueryTerm(boolean casesensitive, String searchmode, 
1058
                     String value) {
1059
      this.casesensitive = casesensitive;
1060
      this.searchmode = searchmode;
1061
      this.value = value;
1062
    }
1063

    
1064
    /**
1065
     * Construct a new instance of a query term for a structured search
1066
     * (matching the value only for those nodes in the pathexpr)
1067
     *
1068
     * @param casesensitive flag indicating whether case is used to match
1069
     * @param searchmode determines what kind of substring match is performed
1070
     *        (one of starts-with|ends-with|contains|matches-exactly)
1071
     * @param value the text value to match
1072
     * @param pathexpr the hierarchical path to the nodes to be searched
1073
     */
1074
    public QueryTerm(boolean casesensitive, String searchmode, 
1075
                     String value, String pathexpr) {
1076
      this(casesensitive, searchmode, value);
1077
      this.pathexpr = pathexpr;
1078
    }
1079

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

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

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

    
1100
    /**
1101
     * create a SQL serialization of the query that this instance represents
1102
     */
1103
    public String printSQL(boolean useXMLIndex) {
1104
      StringBuffer self = new StringBuffer();
1105

    
1106
      // Uppercase the search string if case match is not important
1107
      String casevalue = null;
1108
      String nodedataterm = null;
1109

    
1110
      if (casesensitive) {
1111
        nodedataterm = "nodedata";
1112
        casevalue = value;
1113
      } else {
1114
        nodedataterm = "UPPER(nodedata)";
1115
        casevalue = value.toUpperCase();
1116
      }
1117

    
1118
      // Add appropriate wildcards to search string
1119
      //String searchvalue = null;
1120
      String searchexpr = null;
1121
      if (searchmode.equals("starts-with")) {
1122
        //searchvalue = casevalue + "%";
1123
        searchexpr = nodedataterm + " LIKE '" + casevalue + "%' ";
1124
      } else if (searchmode.equals("ends-with")) {
1125
        //searchvalue = "%" + casevalue;
1126
        searchexpr = nodedataterm + " LIKE '%" + casevalue + "' ";
1127
      } else if (searchmode.equals("contains")) {
1128
        //searchvalue = "%" + casevalue + "%";
1129
        if (!casevalue.equals("%"))
1130
        {
1131
          searchexpr = nodedataterm + " LIKE '%" + casevalue + "%' ";
1132
        }
1133
        else
1134
        {
1135
          searchexpr = nodedataterm + " LIKE '" + casevalue + "' ";
1136
          // find percentage symbol
1137
          percentageSymbol = true;
1138
        }
1139
      } else if (searchmode.equals("equals")) {
1140
        //searchvalue = casevalue;
1141
        searchexpr = nodedataterm + " = '" + casevalue + "' ";
1142
      } else if (searchmode.equals("isnot-equal")) {
1143
        //searchvalue = casevalue;
1144
        searchexpr = nodedataterm + " != '" + casevalue + "' ";
1145
      } else { 
1146
        //searchvalue = casevalue;
1147
        String oper = null;
1148
        if (searchmode.equals("greater-than")) {
1149
          oper = ">";
1150
        } else if (searchmode.equals("greater-than-equals")) {
1151
          oper = ">=";
1152
        } else if (searchmode.equals("less-than")) {
1153
          oper = "<";
1154
        } else if (searchmode.equals("less-than-equals")) {
1155
          oper = "<=";
1156
        } else {
1157
          System.out.println("NOT expected case. NOT recognized operator: " +
1158
                             searchmode);
1159
          return null;
1160
        }
1161
        try {
1162
          // it is number; numeric comparison
1163
          // but we need to make sure there is no string in node data
1164
          String getRidOfString = " AND UPPER(nodedata) = LOWER(nodedata)" +
1165
                                  " AND LTRIM(nodedata) != ' ' " +
1166
                                  " AND nodedata IS NOT NULL ";
1167
          searchexpr = nodedataterm + " " + oper + " " +
1168
                       new Double(casevalue) + " "+getRidOfString;          
1169
        } catch (NumberFormatException nfe) {
1170
          // these are characters; character comparison
1171
          searchexpr = nodedataterm + " " + oper + " '" + casevalue + "' ";
1172
        }
1173
      }
1174

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

    
1179
      if (pathexpr != null) 
1180
      {
1181
        
1182
        // use XML Index
1183
        if ( useXMLIndex ) 
1184
        {
1185
          if (!hasAttributeInPath(pathexpr))
1186
          {
1187
            // without attributes in path
1188
            self.append("AND parentnodeid IN ");
1189
          }
1190
          else
1191
          {
1192
            // has a attribute in path
1193
            String attributeName = getAttributeName(pathexpr);
1194
            self.append("AND nodetype LIKE 'ATTRIBUTE' AND nodename LIKE '"+
1195
                        attributeName + "' ");
1196
            self.append("AND parentnodeid IN ");
1197
            pathexpr = newPathExpressionWithOutAttribute(pathexpr);
1198
            
1199
          } 
1200
          self.append("(SELECT nodeid FROM xml_index WHERE path LIKE " + 
1201
                      "'" +  pathexpr + "') " );
1202
        } 
1203
        else 
1204
        {
1205
          // without using XML Index; using nested statements instead
1206
          self.append("AND parentnodeid IN ");
1207
          self.append(useNestedStatements(pathexpr));
1208
        }
1209
      }
1210
      else
1211
      {
1212
        //if pathexpr is null and search value is %, is a percentageSearchItem
1213
        // the count number will be increase one
1214
        countPercentageSearchItem++;
1215
        
1216
       }
1217

    
1218
      return self.toString();
1219
    }
1220
    
1221
    /* A method to judge if a path have attribute */
1222
    private boolean hasAttributeInPath(String path)
1223
    {
1224
      if (path.indexOf(ATTRIBUTESYMBOL)!=-1)
1225
      {
1226
        return true;
1227
      }
1228
      else
1229
      {
1230
        return false;
1231
      }
1232
    }
1233
    
1234
   
1235
    
1236
    /* 
1237
     * Constraint the query with @pathexp without using the XML Index,
1238
     * but nested SQL statements instead. The query migth be slower.
1239
     */
1240
    private String useNestedStatements(String pathexpr)
1241
    {
1242
      StringBuffer nestedStmts = new StringBuffer();
1243
      Vector nodes = new Vector();
1244
      String path = pathexpr;
1245
      int inx = 0;
1246

    
1247
      do {
1248
        inx = path.lastIndexOf("/");
1249

    
1250
        nodes.addElement(path.substring(inx+1));
1251
        path = path.substring(0, Math.abs(inx));
1252
      } while ( inx > 0 );
1253
      
1254
      // nested statements
1255
      int i = 0;
1256
      for (i = 0; i < nodes.size()-1; i++) {
1257
        nestedStmts.append("(SELECT nodeid FROM xml_nodes" + 
1258
                           " WHERE nodename LIKE '" +
1259
                             (String)nodes.elementAt(i) + "'" +
1260
                           " AND parentnodeid IN ");
1261
      }
1262
      // for the last statement: it is without " AND parentnodeid IN "
1263
      nestedStmts.append("(SELECT nodeid FROM xml_nodes" + 
1264
                         " WHERE nodename LIKE '" +
1265
                         (String)nodes.elementAt(i) + "'" );
1266
      // node.size() number of closing brackets
1267
      for (i = 0; i < nodes.size(); i++) {
1268
        nestedStmts.append(")");
1269
      }
1270

    
1271

    
1272

    
1273
      return nestedStmts.toString();
1274
    }
1275

    
1276
    /**
1277
     * create a String description of the query that this instance represents.
1278
     * This should become a way to get the XML serialization of the query.
1279
     */
1280
    public String toString() {
1281

    
1282
      return this.printSQL(true);
1283
    }
1284
  }
1285
}
(41-41/48)