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-03-04 09:53:49 -0800 (Tue, 04 Mar 2003) $'
14
 * '$Revision: 1447 $'
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
    String allowString = constructAllowString();
207
    allowQuery ="SELECT docid from xml_access WHERE( "+allowString;
208
    allowQuery = allowQuery +") AND subtreeid IS NULL";
209
    MetaCatUtil.debugMessage("allow query is: "+ allowQuery, 30);
210
    return allowQuery;
211
    
212
  
213
  }
214
  
215
  /* Method to construct a allow rule string */
216
  private String constructAllowString()
217
  {
218
    String allowQuery ="";
219
     // add allow rule for user name
220
     if (userName != null && !userName.equals(""))
221
    {
222
      allowQuery = allowQuery +"(principal_name = '" + userName 
223
                              +"' AND perm_type = 'allow'"
224
                              +" AND (permission='4' OR permission='7'))";
225
    }
226
    // add allow rule for public
227
    allowQuery = allowQuery +"OR (principal_name = '" + PUBLIC 
228
                              +"' AND perm_type = 'allow'"
229
                              +" AND (permission='4' OR permission='7'))";
230
    
231
    // add allow rule for group
232
    if (group != null)
233
    {
234
      for (int i = 0; i< group.length; i++)
235
      {
236
        String groupUint = group[i];
237
        if (groupUint != null && !groupUint.equals(""))
238
        {
239
          allowQuery = allowQuery +" OR (principal_name = '" + groupUint 
240
                              +"' AND perm_type = 'allow'"
241
                              +" AND (permission='4' OR permission='7'))";
242
        }//if
243
      }//for
244
    }//if
245
    MetaCatUtil.debugMessage("allow string is: "+ allowQuery, 40);
246
    return allowQuery;
247
  }
248

    
249
   /*
250
   * Method to create query for xml_access, this part is to get docid list which
251
   * have a deny rule and perm_order is allowFirst for a given user. This means
252
   * the user will be denied to read
253
   */
254
  private String createDenyRuleQuery()
255
  {
256
    String denyQuery = null;
257
    String denyString = constructDenyString();
258
    denyQuery ="SELECT docid from xml_access WHERE( " + denyString;
259
    denyQuery = denyQuery + ") AND subtreeid IS NULL ";
260
    MetaCatUtil.debugMessage("denyquery is: "+ denyQuery, 30);
261
    return denyQuery;
262
  
263
  }
264
  /* Construct deny string */
265
  private String constructDenyString()
266
  {
267
    String denyQuery ="";
268
    // add deny rule for user name
269
    if (userName != null && !userName.equals(""))
270
    {
271
      denyQuery = denyQuery +"(principal_name = '" + userName 
272
                              +"' AND perm_type = 'deny' "
273
                              +"AND perm_order ='allowFirst'"
274
                              +" AND (permission='4' OR permission='7'))";
275
    }
276
    // add deny rule for public
277
    denyQuery = denyQuery +"OR (principal_name = '" + PUBLIC 
278
                               +"' AND perm_type = 'deny' "
279
                               +"AND perm_order ='allowFirst'"
280
                               +" AND (permission='4' OR permission='7'))";
281
    
282
    // add allow rule for group
283
    if (group != null)
284
    {
285
      for (int i = 0; i< group.length; i++)
286
      {
287
        String groupUint = group[i];
288
        if (groupUint != null && !groupUint.equals(""))
289
        {
290
          denyQuery = denyQuery +" OR (principal_name = '" + groupUint 
291
                                +"' AND perm_type = 'deny' "
292
                                +"AND perm_order ='allowFirst'"
293
                                +" AND (permission='4' OR permission='7'))";
294
        }//if
295
      }//for
296
    }//if
297
    return denyQuery;
298
  }
299
  
300
  /**
301
   * Method to append a access control query to SQL. So in DBQuery class, we can
302
   * get docid from both user specified query and access control query. We don't
303
   * need to checking permission after we get the doclist. It will be good to 
304
   * performance
305
   *
306
   */
307
  public String getAccessQuery()
308
  {
309
    String accessQuery = null;
310
    String onwer = createOwerQuery();
311
    String allow = createAllowRuleQuery();
312
    String deny = createDenyRuleQuery();
313
    accessQuery = " AND (docid IN("+ onwer + ")";
314
    accessQuery = accessQuery + " OR (docid IN (" + allow + ")" 
315
                 + " AND docid NOT IN ("+ deny + ")))";
316
    MetaCatUtil.debugMessage("accessquery is: "+ accessQuery, 30);
317
    return accessQuery;
318
  }
319
  
320
  /** Main routine for testing */
321
  static public void main(String[] args) {
322

    
323
     if (args.length < 1) {
324
       System.err.println("Wrong number of arguments!!!");
325
       System.err.println("USAGE: java QuerySpecification <xmlfile>");
326
       return;
327
     } else {
328
       int i = 0;
329
       boolean useXMLIndex = true;
330
       if ( args[i].equals( "-noindex" ) ) {
331
         useXMLIndex = false;
332
         i++;
333
       }
334
       String xmlfile  = args[i];
335

    
336
       try {
337
         MetaCatUtil util = new MetaCatUtil();
338
         FileReader xml = new FileReader(new File(xmlfile));
339
         QuerySpecification qspec = 
340
                 new QuerySpecification(xml, util.getOption("saxparser"),
341
                                        util.getOption("accNumberSeparator"));
342
         System.out.println(qspec.printSQL(useXMLIndex));
343

    
344
       } catch (IOException e) {
345
         System.err.println(e.getMessage());
346
       }
347
         
348
     }
349
  }
350
  
351
  /**
352
   * Returns true if the parsed query contains and extended xml query 
353
   * (i.e. there is at least one &lt;returnfield&gt; in the pathquery document)
354
   */
355
  public boolean containsExtendedSQL()
356
  {
357
    if(containsExtendedSQL)
358
    {
359
      return true;
360
    }
361
    else
362
    {
363
      return false;
364
    }
365
  }
366
  
367
  /**
368
   * A method to get if the query has an attribute return field
369
   */
370
  public boolean containAttributeReturnField()
371
  {
372
    return hasAttributeReturnField;
373
  }
374
  
375
  /**
376
   * Accessor method to return the identifier of this Query
377
   */
378
  public String getIdentifier()
379
  {
380
    return meta_file_id;
381
  }
382

    
383
  /**
384
   * method to set the identifier of this query
385
   */
386
  public void setIdentifier(String id) {
387
    this.meta_file_id = id;
388
  }
389

    
390
  /**
391
   * Accessor method to return the title of this Query
392
   */
393
  public String getQueryTitle()
394
  {
395
    return queryTitle;
396
  }
397

    
398
  /**
399
   * method to set the title of this query
400
   */
401
  public void setQueryTitle(String title)
402
  {
403
    this.queryTitle = title;
404
  }
405

    
406
  /**
407
   * Accessor method to return a vector of the return document types as
408
   * defined in the &lt;returndoctype&gt; tag in the pathquery dtd.
409
   */
410
  public Vector getReturnDocList()
411
  {
412
    return this.returnDocList;
413
  }
414

    
415
  /**
416
   * method to set the list of return docs of this query
417
   */
418
  public void setReturnDocList(Vector returnDocList)
419
  {
420
    this.returnDocList = returnDocList;
421
  }
422

    
423
  /**
424
   * Accessor method to return a vector of the filter doc types as
425
   * defined in the &lt;filterdoctype&gt; tag in the pathquery dtd.
426
   */
427
  public Vector getFilterDocList()
428
  {
429
    return this.filterDocList;
430
  }
431

    
432
  /**
433
   * method to set the list of filter docs of this query
434
   */
435
  public void setFilterDocList(Vector filterDocList)
436
  {
437
    this.filterDocList = filterDocList;
438
  }
439

    
440
  /**
441
   * Accessor method to return a vector of the extended return fields as
442
   * defined in the &lt;returnfield&gt; tag in the pathquery dtd.
443
   */
444
  public Vector getReturnFieldList()
445
  {
446
    return this.returnFieldList; 
447
  }
448

    
449
  /**
450
   * method to set the list of fields to be returned by this query
451
   */
452
  public void setReturnFieldList(Vector returnFieldList)
453
  {
454
    this.returnFieldList = returnFieldList;
455
  }
456

    
457
  /**
458
   * Accessor method to return a vector of the owner fields as
459
   * defined in the &lt;owner&gt; tag in the pathquery dtd.
460
   */
461
  public Vector getOwnerList()
462
  {
463
    return this.ownerList;
464
  }
465

    
466
  /**
467
   * method to set the list of owners used to constrain this query
468
   */
469
  public void setOwnerList(Vector ownerList)
470
  {
471
    this.ownerList = ownerList;
472
  }
473

    
474
  /**
475
   * Accessor method to return a vector of the site fields as
476
   * defined in the &lt;site&gt; tag in the pathquery dtd.
477
   */
478
  public Vector getSiteList()
479
  {
480
    return this.siteList;
481
  }
482

    
483
  /**
484
   * method to set the list of sites used to constrain this query
485
   */
486
  public void setSiteList(Vector siteList)
487
  {
488
    this.siteList = siteList;
489
  }
490

    
491
  /**
492
   * get the QueryGroup used to express query constraints
493
   */
494
  public QueryGroup getQueryGroup()
495
  {
496
    return query;
497
  }
498

    
499
  /**
500
   * Set up the SAX parser for reading the XML serialized query
501
   */
502
  private XMLReader initializeParser() {
503
    XMLReader parser = null;
504

    
505
    // Set up the SAX document handlers for parsing
506
    try {
507

    
508
      // Get an instance of the parser
509
      parser = XMLReaderFactory.createXMLReader(parserName);
510

    
511
      // Set the ContentHandler to this instance
512
      parser.setContentHandler(this);
513

    
514
      // Set the error Handler to this instance
515
      parser.setErrorHandler(this);
516

    
517
    } catch (Exception e) {
518
       System.err.println("Error in QuerySpcecification.initializeParser " + 
519
                           e.toString());
520
    }
521

    
522
    return parser;
523
  }
524

    
525
  /**
526
   * callback method used by the SAX Parser when the start tag of an 
527
   * element is detected. Used in this context to parse and store
528
   * the query information in class variables.
529
   */
530
  public void startElement (String uri, String localName, 
531
                            String qName, Attributes atts) 
532
         throws SAXException {
533
    BasicNode currentNode = new BasicNode(localName);
534
    // add attributes to BasicNode here
535
    if (atts != null) {
536
      int len = atts.getLength();
537
      for (int i = 0; i < len; i++) {
538
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
539
      }
540
    }
541

    
542
    elementStack.push(currentNode); 
543
    if (currentNode.getTagName().equals("querygroup")) {
544
      QueryGroup currentGroup = new QueryGroup(
545
                                currentNode.getAttribute("operator"));
546
      if (query == null) {
547
        query = currentGroup;
548
      } else {
549
        QueryGroup parentGroup = (QueryGroup)queryStack.peek();
550
        parentGroup.addChild(currentGroup);
551
      }
552
      queryStack.push(currentGroup);
553
    }
554
  }
555

    
556
  /**
557
   * callback method used by the SAX Parser when the end tag of an 
558
   * element is detected. Used in this context to parse and store
559
   * the query information in class variables.
560
   */
561
  public void endElement (String uri, String localName,
562
                          String qName) throws SAXException {
563
    BasicNode leaving = (BasicNode)elementStack.pop(); 
564
    if (leaving.getTagName().equals("queryterm")) {
565
      boolean isCaseSensitive = (new Boolean(
566
              leaving.getAttribute("casesensitive"))).booleanValue();
567
      QueryTerm currentTerm = null;
568
      if (currentPathexpr == null) {
569
        currentTerm = new QueryTerm(isCaseSensitive,
570
                      leaving.getAttribute("searchmode"),currentValue);
571
      } else {
572
        currentTerm = new QueryTerm(isCaseSensitive,
573
                      leaving.getAttribute("searchmode"),currentValue,
574
                      currentPathexpr);
575
      }
576
      QueryGroup currentGroup = (QueryGroup)queryStack.peek();
577
      currentGroup.addChild(currentTerm);
578
      currentValue = null;
579
      currentPathexpr = null;
580
    } else if (leaving.getTagName().equals("querygroup")) {
581
      QueryGroup leavingGroup = (QueryGroup)queryStack.pop();
582
    }
583
  }
584

    
585
  /**
586
   * callback method used by the SAX Parser when the text sequences of an 
587
   * xml stream are detected. Used in this context to parse and store
588
   * the query information in class variables.
589
   */
590
  public void characters(char ch[], int start, int length) {
591

    
592
    String inputString = new String(ch, start, length);
593
    BasicNode currentNode = (BasicNode)elementStack.peek(); 
594
    String currentTag = currentNode.getTagName();
595
    if (currentTag.equals("meta_file_id")) {
596
      meta_file_id = inputString;
597
    } else if (currentTag.equals("querytitle")) {
598
      queryTitle = inputString;
599
    } else if (currentTag.equals("value")) {
600
      currentValue = inputString;
601
    } else if (currentTag.equals("pathexpr")) {
602
      currentPathexpr = inputString;
603
    } else if (currentTag.equals("returndoctype")) {
604
      returnDocList.add(inputString);
605
    } else if (currentTag.equals("filterdoctype")) {
606
      filterDocList.add(inputString);
607
    } else if (currentTag.equals("returnfield")) {
608
      // make sure if return fields has an attribute or not
609
      if (inputString.indexOf(ATTRIBUTESYMBOL) ==-1)
610
      {
611
        // no attribute value will be returned
612
        returnFieldList.add(inputString);
613
        containsExtendedSQL = true;
614
      }
615
      else
616
      {
617
        // has a attribute return field
618
        // divied the return filed into two parts, one is path and the
619
        // other is attribue name
620
        String returnPath = newPathExpressionWithOutAttribute(inputString);
621
        String attributeName = getAttributeName(inputString);
622
        Vector pathInfo = new Vector();
623
        // the vector has the information about return path and attributename
624
        pathInfo.addElement(returnPath);
625
        pathInfo.addElement(attributeName);
626
        // put the vector into a hash table. The reseaon why don't put
627
        // return path or attributename as a key is because they are not unique
628
        attributeReturnList.put
629
                            (new Integer(countAttributeReturnField), pathInfo);
630
        countAttributeReturnField++;
631
        hasAttributeReturnField = true;
632
        containsExtendedSQL = true;
633
        
634
      }
635
    } else if (currentTag.equals("filterdoctype")) {
636
      filterDocList.add(inputString);
637
    } else if (currentTag.equals("owner")) {
638
      ownerList.add(inputString);
639
    } else if (currentTag.equals("site")) {
640
      siteList.add(inputString);
641
    }
642
  }
643

    
644
  /**
645
   * create a SQL serialization of the query that this instance represents
646
   */
647
  public String printSQL(boolean useXMLIndex) {
648
    
649
   
650
    StringBuffer self = new StringBuffer();
651

    
652
    self.append("SELECT docid,docname,doctype,");
653
    self.append("date_created, date_updated, rev ");
654
    self.append("FROM xml_documents WHERE docid IN (");
655

    
656
    // This determines the documents that meet the query conditions
657
    self.append(query.printSQL(useXMLIndex));
658

    
659
    self.append(") ");
660
 
661
    // Add SQL to filter for doctypes requested in the query
662
    // This is an implicit OR for the list of doctypes. Only doctypes in this
663
    // list will be searched if the tag is present
664
    if (!filterDocList.isEmpty()) {
665
      boolean firstdoctype = true;
666
      self.append(" AND ("); 
667
      Enumeration en = filterDocList.elements();
668
      while (en.hasMoreElements()) {
669
        String currentDoctype = (String)en.nextElement();
670
        if (firstdoctype) {
671
           firstdoctype = false;
672
           self.append(" doctype = '" + currentDoctype + "'"); 
673
        } else {
674
          self.append(" OR doctype = '" + currentDoctype + "'"); 
675
        }
676
      }
677
      self.append(") ");
678
    }
679

    
680
    // Add SQL to filter for owners requested in the query
681
    // This is an implicit OR for the list of owners
682
    if (!ownerList.isEmpty()) {
683
      boolean first = true;
684
      self.append(" AND ("); 
685
      Enumeration en = ownerList.elements();
686
      while (en.hasMoreElements()) {
687
        String current = (String)en.nextElement();
688
        if (first) {
689
           first = false;
690
           self.append(" user_owner = '" + current + "'"); 
691
        } else {
692
          self.append(" OR user_owner = '" + current + "'"); 
693
        }
694
      }
695
      self.append(") ");
696
    }
697

    
698
    // Add SQL to filter for sites requested in the query
699
    // This is an implicit OR for the list of sites
700
    if (!siteList.isEmpty()) {
701
      boolean first = true;
702
      self.append(" AND ("); 
703
      Enumeration en = siteList.elements();
704
      while (en.hasMoreElements()) {
705
        String current = (String)en.nextElement();
706
        if (first) {
707
           first = false;
708
           self.append(" SUBSTR(docid, 1, INSTR(docid, '" +
709
               accNumberSeparator + "')-1) = '" + current + "'"); 
710
        } else {
711
          self.append(" OR SUBSTR(docid, 1, INSTR(docid, '" +
712
               accNumberSeparator + "')-1) = '" + current + "'"); 
713
        }
714
      }
715
      self.append(") ");
716
    }
717
    
718
    // if there is only one percentage search item, this query is a percentage
719
    // search query
720
    if (countPercentageSearchItem ==1)
721
    {
722
      
723
      percentageSearch =true;
724
    }
725
    
726
    return self.toString();
727
  }
728
  
729
  /** This sql command will selecet startnodeid and endnodeid that user can NOT
730
    * access
731
   */
732
  public String printAccessControlSQLForReturnField(String doclist)
733
  {
734
    StringBuffer sql = new StringBuffer();
735
    String allowString = constructAllowString();
736
    String denyString = constructDenyString();
737
    sql.append("SELECT startnodeid, endnodeid from xml_access ");
738
    sql.append("WHERE docid in (");
739
    sql.append(doclist);
740
    sql.append(") AND subtreeid IS NOT NULL AND ");
741
    sql.append("(");
742
    sql.append("(subtreeid NOT IN (SELECT subtreeid from xml_access where ");
743
    sql.append(allowString);
744
    sql.append(")");
745
    sql.append(")");
746
    sql.append(" OR (subtreeid IN (SELECT subtreeid from xml_access where ");
747
    sql.append(denyString);
748
    sql.append(")");
749
    sql.append(")");
750
    sql.append(")");
751
    MetaCatUtil.debugMessage("accessControlSQLForReturnField: " +
752
                             sql.toString(), 30);
753
    return sql.toString();
754
  }
755
  
756
  /**
757
   * This method prints sql based upon the &lt;returnfield&gt; tag in the
758
   * pathquery document.  This allows for customization of the 
759
   * returned fields
760
   * @param doclist the list of document ids to search by
761
   */
762
  public String printExtendedSQL(String doclist)
763
  {  
764
    StringBuffer self = new StringBuffer();
765
    self.append("select xml_nodes.docid, xml_index.path, xml_nodes.nodedata, ");
766
    self.append("xml_nodes.parentnodeid ");
767
    self.append("from xml_index, xml_nodes where xml_index.nodeid=");
768
    self.append("xml_nodes.parentnodeid and (xml_index.path like '");
769
    boolean firstfield = true;
770
    //put the returnfields into the query
771
    //the for loop allows for multiple fields
772
    for(int i=0; i<returnFieldList.size(); i++)
773
    {
774
      if(firstfield)
775
      {
776
        firstfield = false;
777
        self.append((String)returnFieldList.elementAt(i));
778
        self.append("' ");
779
      }
780
      else
781
      {
782
        self.append("or xml_index.path like '");
783
        self.append((String)returnFieldList.elementAt(i));
784
        self.append("' ");
785
      }
786
    }
787
    self.append(") AND xml_nodes.docid in (");
788
    //self.append(query.printSQL());
789
    self.append(doclist);
790
    self.append(")");
791
    self.append(" AND xml_nodes.nodetype = 'TEXT'");
792

    
793
   
794
    return self.toString();
795
  }
796
  
797
  /**
798
   * This method prints sql based upon the returnfield tag in the
799
   * pathquery document has an attribute.  This allows for customization of the 
800
   * returned fields
801
   * @param doclist the list of document ids to search by
802
   */
803
  public String printAttributeQuery(String doclist)
804
  {
805
    StringBuffer self = new StringBuffer();
806
    self.append("select xml_nodes.docid, xml_index.path, ");
807
    self.append("xml_nodes.nodedata, xml_nodes.nodename ");
808
    self.append("from xml_index, xml_nodes where xml_index.nodeid=");
809
    self.append("xml_nodes.parentnodeid and (");
810
    boolean firstfield = true;
811
    //put the returnfields attributes into the query
812
    //the for loop allows for multiple fields and attributes
813
    Enumeration returnAttributes = attributeReturnList.elements();
814
    while (returnAttributes.hasMoreElements())
815
    {
816
      Vector currentVector = (Vector)returnAttributes.nextElement();
817
      String returnPath = (String)currentVector.elementAt(0);
818
      String attributeName = (String)currentVector.elementAt(1);
819
      if(firstfield)
820
      {
821
        firstfield = false;
822
        self.append("( xml_index.path like '");
823
        self.append(returnPath);
824
        self.append("' AND xml_nodes.nodename like '");
825
        self.append(attributeName);
826
        self.append("') ");
827
      }
828
      else
829
      {
830
        self.append(" or ( xml_index.path like '");
831
        self.append(returnPath);
832
        self.append("' AND xml_nodes.nodename like '");
833
        self.append(attributeName);
834
        self.append("') "); 
835
      }
836
    }
837
    self.append(") AND xml_nodes.docid in (");
838
    //self.append(query.printSQL());
839
    self.append(doclist);
840
    self.append(")");
841
    self.append(" AND xml_nodes.nodetype = 'ATTRIBUTE'");
842
    MetaCatUtil.debugMessage("Attribute query: "+self.toString(), 30);
843
   
844
    return self.toString();
845
  }
846
  
847
  public static String printRelationSQL(String docid)
848
  {
849
    StringBuffer self = new StringBuffer();
850
    self.append("select subject, relationship, object, subdoctype, ");
851
    self.append("objdoctype from xml_relation ");
852
    self.append("where docid like '").append(docid).append("'");
853
    return self.toString();
854
  }
855
   
856
  /**
857
   * Prints sql that returns all relations in the database.
858
   */
859
  public static String printPackageSQL()
860
  {
861
    StringBuffer self = new StringBuffer();
862
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
863
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
864
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
865
    self.append("from xml_index where path like ");
866
    self.append("'triple/relationship') rel, ");
867
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
868
    self.append("'triple/object') o, ");
869
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
870
    self.append("where s.parentnodeid = rel.parentnodeid ");
871
    self.append("and rel.parentnodeid = o.parentnodeid ");
872
    self.append("and x.parentnodeid in (rel.nodeid) ");
873
    self.append("and y.parentnodeid in (o.nodeid) ");
874
    self.append("and z.parentnodeid in (s.nodeid) ");
875
    //self.append("and z.nodedata like '%");
876
    //self.append(docid);
877
    //self.append("%'");
878
    return self.toString();
879
  }
880
  
881
  /**
882
   * Prints sql that returns all relations in the database that were input
883
   * under a specific docid
884
   * @param docid the docid to search for.
885
   */
886
  public static String printPackageSQL(String docid)
887
  {
888
    StringBuffer self = new StringBuffer();
889
    self.append("select z.nodedata, z.parentnodeid, ");
890
    self.append("x.nodedata, x.parentnodeid, y.nodedata, y.parentnodeid from ");
891
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
892
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
893
    self.append("from xml_index where path like ");
894
    self.append("'triple/relationship') rel, ");
895
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
896
    self.append("'triple/object') o, ");
897
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
898
    self.append("where s.parentnodeid = rel.parentnodeid ");
899
    self.append("and rel.parentnodeid = o.parentnodeid ");
900
    self.append("and x.parentnodeid in (rel.nodeid) ");
901
    self.append("and y.parentnodeid in (o.nodeid) ");
902
    self.append("and z.parentnodeid in (s.nodeid) ");
903
    self.append("and z.docid like '").append(docid).append("'");
904
    
905
    return self.toString();
906
  }
907
  
908
  /**
909
   * Returns all of the relations that has a certain docid in the subject
910
   * or the object.
911
   * 
912
   * @param docid the docid to search for
913
   */
914
  public static String printPackageSQL(String subDocidURL, String objDocidURL)
915
  {
916
    StringBuffer self = new StringBuffer();
917
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
918
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
919
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
920
    self.append("from xml_index where path like ");
921
    self.append("'triple/relationship') rel, ");
922
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
923
    self.append("'triple/object') o, ");
924
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
925
    self.append("where s.parentnodeid = rel.parentnodeid ");
926
    self.append("and rel.parentnodeid = o.parentnodeid ");
927
    self.append("and x.parentnodeid in (rel.nodeid) ");
928
    self.append("and y.parentnodeid in (o.nodeid) ");
929
    self.append("and z.parentnodeid in (s.nodeid) ");
930
    self.append("and (z.nodedata like '");
931
    self.append(subDocidURL);
932
    self.append("' or y.nodedata like '");
933
    self.append(objDocidURL);
934
    self.append("')");
935
    return self.toString();
936
  }
937
  
938
  public static String printGetDocByDoctypeSQL(String docid)
939
  {
940
    StringBuffer self = new StringBuffer();
941

    
942
    self.append("SELECT docid,docname,doctype,");
943
    self.append("date_created, date_updated ");
944
    self.append("FROM xml_documents WHERE docid IN (");
945
    self.append(docid).append(")");
946
    return self.toString();
947
  }
948
  
949
  /**
950
   * create a String description of the query that this instance represents.
951
   * This should become a way to get the XML serialization of the query.
952
   */
953
  public String toString() {
954
    return "meta_file_id=" + meta_file_id + "\n" + query;
955
//DOCTITLE attr cleared from the db
956
//    return "meta_file_id=" + meta_file_id + "\n" + 
957
//           "querytitle=" + querytitle + "\n" + query;
958
  }
959
  
960
  /* A method to get rid of attribute part in path expression*/
961
  public static String newPathExpressionWithOutAttribute(String pathExpression)
962
  {
963
      if (pathExpression == null)
964
      {
965
        return null;
966
      }
967
      int index = pathExpression.lastIndexOf(ATTRIBUTESYMBOL);
968
      String newExpression = null;
969
      if (index != 1)
970
      {
971
        newExpression=pathExpression.substring(0, index-1);
972
      } 
973
      MetaCatUtil.debugMessage("The path expression without attributes: )" + 
974
                               newExpression, 30);
975
      return newExpression;
976
  }
977
    
978
  /* A method to get attribute name from path */
979
  public static String getAttributeName(String path)
980
  {
981
      if (path == null)
982
      {
983
        return null;
984
      }
985
      int index = path.lastIndexOf(ATTRIBUTESYMBOL);
986
      int size = path.length();
987
      String attributeName = null;
988
       if (index != 1)
989
      {
990
        attributeName = path.substring(index+1, size);
991
      } 
992
      MetaCatUtil.debugMessage("The attirbute name from path: )" + 
993
                               attributeName, 30);
994
      return attributeName;
995
  }
996

    
997
  /** a utility class that represents a group of terms in a query */
998
  private class QueryGroup {
999
    private String operator = null;  // indicates how query terms are combined
1000
    private Vector children = null;  // the list of query terms and groups
1001

    
1002
    /** 
1003
     * construct a new QueryGroup 
1004
     *
1005
     * @param operator the boolean conector used to connect query terms 
1006
     *                    in this query group
1007
     */
1008
    public QueryGroup(String operator) {
1009
      this.operator = operator;
1010
      children = new Vector();
1011
    }
1012

    
1013
    /** 
1014
     * Add a child QueryGroup to this QueryGroup
1015
     *
1016
     * @param qgroup the query group to be added to the list of terms
1017
     */
1018
    public void addChild(QueryGroup qgroup) {
1019
      children.add((Object)qgroup); 
1020
    }
1021

    
1022
    /**
1023
     * Add a child QueryTerm to this QueryGroup
1024
     *
1025
     * @param qterm the query term to be added to the list of terms
1026
     */
1027
    public void addChild(QueryTerm qterm) {
1028
      children.add((Object)qterm); 
1029
    }
1030

    
1031
    /**
1032
     * Retrieve an Enumeration of query terms for this QueryGroup
1033
     */
1034
    public Enumeration getChildren() {
1035
      return children.elements();
1036
    }
1037
   
1038
    /**
1039
     * create a SQL serialization of the query that this instance represents
1040
     */
1041
    public String printSQL(boolean useXMLIndex) {
1042
      StringBuffer self = new StringBuffer();
1043
      boolean first = true;
1044

    
1045
      self.append("(");
1046

    
1047
      Enumeration en= getChildren();
1048
      while (en.hasMoreElements()) {
1049
        Object qobject = en.nextElement();
1050
        if (first) {
1051
          first = false;
1052
        } else {
1053
          self.append(" " + operator + " ");
1054
        }
1055
        if (qobject instanceof QueryGroup) {
1056
          QueryGroup qg = (QueryGroup)qobject;
1057
          self.append(qg.printSQL(useXMLIndex));
1058
        } else if (qobject instanceof QueryTerm) {
1059
          QueryTerm qt = (QueryTerm)qobject;
1060
          self.append(qt.printSQL(useXMLIndex));
1061
        } else {
1062
          System.err.println("qobject wrong type: fatal error");
1063
        }
1064
      }
1065
      self.append(") \n");
1066
      return self.toString();
1067
    }
1068

    
1069
    /**
1070
     * create a String description of the query that this instance represents.
1071
     * This should become a way to get the XML serialization of the query.
1072
     */
1073
    public String toString() {
1074
      StringBuffer self = new StringBuffer();
1075

    
1076
      self.append("  (Query group operator=" + operator + "\n");
1077
      Enumeration en= getChildren();
1078
      while (en.hasMoreElements()) {
1079
        Object qobject = en.nextElement();
1080
        self.append(qobject);
1081
      }
1082
      self.append("  )\n");
1083
      return self.toString();
1084
    }
1085
  }
1086

    
1087
  /** a utility class that represents a single term in a query */
1088
  private class QueryTerm {
1089
    private boolean casesensitive = false;
1090
    private String searchmode = null;
1091
    private String value = null;
1092
    private String pathexpr = null;
1093
    private boolean percentageSymbol = false;
1094
   
1095

    
1096
    /**
1097
     * Construct a new instance of a query term for a free text search
1098
     * (using the value only)
1099
     *
1100
     * @param casesensitive flag indicating whether case is used to match
1101
     * @param searchmode determines what kind of substring match is performed
1102
     *        (one of starts-with|ends-with|contains|matches-exactly)
1103
     * @param value the text value to match
1104
     */
1105
    public QueryTerm(boolean casesensitive, String searchmode, 
1106
                     String value) {
1107
      this.casesensitive = casesensitive;
1108
      this.searchmode = searchmode;
1109
      this.value = value;
1110
    }
1111

    
1112
    /**
1113
     * Construct a new instance of a query term for a structured search
1114
     * (matching the value only for those nodes in the pathexpr)
1115
     *
1116
     * @param casesensitive flag indicating whether case is used to match
1117
     * @param searchmode determines what kind of substring match is performed
1118
     *        (one of starts-with|ends-with|contains|matches-exactly)
1119
     * @param value the text value to match
1120
     * @param pathexpr the hierarchical path to the nodes to be searched
1121
     */
1122
    public QueryTerm(boolean casesensitive, String searchmode, 
1123
                     String value, String pathexpr) {
1124
      this(casesensitive, searchmode, value);
1125
      this.pathexpr = pathexpr;
1126
    }
1127

    
1128
    /** determine if the QueryTerm is case sensitive */
1129
    public boolean isCaseSensitive() {
1130
      return casesensitive;
1131
    }
1132

    
1133
    /** get the searchmode parameter */
1134
    public String getSearchMode() {
1135
      return searchmode;
1136
    }
1137
 
1138
    /** get the Value parameter */
1139
    public String getValue() {
1140
      return value;
1141
    }
1142

    
1143
    /** get the path expression parameter */
1144
    public String getPathExpression() {
1145
      return pathexpr;
1146
    }
1147

    
1148
    /**
1149
     * create a SQL serialization of the query that this instance represents
1150
     */
1151
    public String printSQL(boolean useXMLIndex) {
1152
      StringBuffer self = new StringBuffer();
1153

    
1154
      // Uppercase the search string if case match is not important
1155
      String casevalue = null;
1156
      String nodedataterm = null;
1157

    
1158
      if (casesensitive) {
1159
        nodedataterm = "nodedata";
1160
        casevalue = value;
1161
      } else {
1162
        nodedataterm = "UPPER(nodedata)";
1163
        casevalue = value.toUpperCase();
1164
      }
1165

    
1166
      // Add appropriate wildcards to search string
1167
      //String searchvalue = null;
1168
      String searchexpr = null;
1169
      if (searchmode.equals("starts-with")) {
1170
        //searchvalue = casevalue + "%";
1171
        searchexpr = nodedataterm + " LIKE '" + casevalue + "%' ";
1172
      } else if (searchmode.equals("ends-with")) {
1173
        //searchvalue = "%" + casevalue;
1174
        searchexpr = nodedataterm + " LIKE '%" + casevalue + "' ";
1175
      } else if (searchmode.equals("contains")) {
1176
        //searchvalue = "%" + casevalue + "%";
1177
        if (!casevalue.equals("%"))
1178
        {
1179
          searchexpr = nodedataterm + " LIKE '%" + casevalue + "%' ";
1180
        }
1181
        else
1182
        {
1183
          searchexpr = nodedataterm + " LIKE '" + casevalue + "' ";
1184
          // find percentage symbol
1185
          percentageSymbol = true;
1186
        }
1187
      } else if (searchmode.equals("equals")) {
1188
        //searchvalue = casevalue;
1189
        searchexpr = nodedataterm + " = '" + casevalue + "' ";
1190
      } else if (searchmode.equals("isnot-equal")) {
1191
        //searchvalue = casevalue;
1192
        searchexpr = nodedataterm + " != '" + casevalue + "' ";
1193
      } else { 
1194
        //searchvalue = casevalue;
1195
        String oper = null;
1196
        if (searchmode.equals("greater-than")) {
1197
          oper = ">";
1198
        } else if (searchmode.equals("greater-than-equals")) {
1199
          oper = ">=";
1200
        } else if (searchmode.equals("less-than")) {
1201
          oper = "<";
1202
        } else if (searchmode.equals("less-than-equals")) {
1203
          oper = "<=";
1204
        } else {
1205
          System.out.println("NOT expected case. NOT recognized operator: " +
1206
                             searchmode);
1207
          return null;
1208
        }
1209
        try {
1210
          // it is number; numeric comparison
1211
          // but we need to make sure there is no string in node data
1212
          String getRidOfString = " AND UPPER(nodedata) = LOWER(nodedata)" +
1213
                                  " AND LTRIM(nodedata) != ' ' " +
1214
                                  " AND nodedata IS NOT NULL ";
1215
          searchexpr = nodedataterm + " " + oper + " " +
1216
                       new Double(casevalue) + " "+getRidOfString;          
1217
        } catch (NumberFormatException nfe) {
1218
          // these are characters; character comparison
1219
          searchexpr = nodedataterm + " " + oper + " '" + casevalue + "' ";
1220
        }
1221
      }
1222

    
1223
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
1224
      //self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
1225
      self.append(searchexpr);
1226

    
1227
      if (pathexpr != null) 
1228
      {
1229
        
1230
        // use XML Index
1231
        if ( useXMLIndex ) 
1232
        {
1233
          if (!hasAttributeInPath(pathexpr))
1234
          {
1235
            // without attributes in path
1236
            self.append("AND parentnodeid IN ");
1237
          }
1238
          else
1239
          {
1240
            // has a attribute in path
1241
            String attributeName = getAttributeName(pathexpr);
1242
            self.append("AND nodetype LIKE 'ATTRIBUTE' AND nodename LIKE '"+
1243
                        attributeName + "' ");
1244
            self.append("AND parentnodeid IN ");
1245
            pathexpr = newPathExpressionWithOutAttribute(pathexpr);
1246
            
1247
          } 
1248
          self.append("(SELECT nodeid FROM xml_index WHERE path LIKE " + 
1249
                      "'" +  pathexpr + "') " );
1250
        } 
1251
        else 
1252
        {
1253
          // without using XML Index; using nested statements instead
1254
          self.append("AND parentnodeid IN ");
1255
          self.append(useNestedStatements(pathexpr));
1256
        }
1257
      }
1258
      else
1259
      {
1260
        //if pathexpr is null and search value is %, is a percentageSearchItem
1261
        // the count number will be increase one
1262
        countPercentageSearchItem++;
1263
        
1264
       }
1265

    
1266
      return self.toString();
1267
    }
1268
    
1269
    /* A method to judge if a path have attribute */
1270
    private boolean hasAttributeInPath(String path)
1271
    {
1272
      if (path.indexOf(ATTRIBUTESYMBOL)!=-1)
1273
      {
1274
        return true;
1275
      }
1276
      else
1277
      {
1278
        return false;
1279
      }
1280
    }
1281
    
1282
   
1283
    
1284
    /* 
1285
     * Constraint the query with @pathexp without using the XML Index,
1286
     * but nested SQL statements instead. The query migth be slower.
1287
     */
1288
    private String useNestedStatements(String pathexpr)
1289
    {
1290
      StringBuffer nestedStmts = new StringBuffer();
1291
      Vector nodes = new Vector();
1292
      String path = pathexpr;
1293
      int inx = 0;
1294

    
1295
      do {
1296
        inx = path.lastIndexOf("/");
1297

    
1298
        nodes.addElement(path.substring(inx+1));
1299
        path = path.substring(0, Math.abs(inx));
1300
      } while ( inx > 0 );
1301
      
1302
      // nested statements
1303
      int i = 0;
1304
      for (i = 0; i < nodes.size()-1; i++) {
1305
        nestedStmts.append("(SELECT nodeid FROM xml_nodes" + 
1306
                           " WHERE nodename LIKE '" +
1307
                             (String)nodes.elementAt(i) + "'" +
1308
                           " AND parentnodeid IN ");
1309
      }
1310
      // for the last statement: it is without " AND parentnodeid IN "
1311
      nestedStmts.append("(SELECT nodeid FROM xml_nodes" + 
1312
                         " WHERE nodename LIKE '" +
1313
                         (String)nodes.elementAt(i) + "'" );
1314
      // node.size() number of closing brackets
1315
      for (i = 0; i < nodes.size(); i++) {
1316
        nestedStmts.append(")");
1317
      }
1318

    
1319

    
1320

    
1321
      return nestedStmts.toString();
1322
    }
1323

    
1324
    /**
1325
     * create a String description of the query that this instance represents.
1326
     * This should become a way to get the XML serialization of the query.
1327
     */
1328
    public String toString() {
1329

    
1330
      return this.printSQL(true);
1331
    }
1332
  }
1333
}
(45-45/54)