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 12:06:04 -0800 (Tue, 04 Mar 2003) $'
14
 * '$Revision: 1451 $'
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 distinct 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
   * @param unaccessableNodePair  the node pair(start id and end id) which this
762
   *                               user could not access it
763
   */
764
  public String printExtendedSQL(String doclist, Hashtable unaccessableNodePair)
765
  {  
766
    StringBuffer self = new StringBuffer();
767
    self.append("select xml_nodes.docid, xml_index.path, xml_nodes.nodedata, ");
768
    self.append("xml_nodes.parentnodeid ");
769
    self.append("from xml_index, xml_nodes where xml_index.nodeid=");
770
    self.append("xml_nodes.parentnodeid and (xml_index.path like '");
771
    boolean firstfield = true;
772
    //put the returnfields into the query
773
    //the for loop allows for multiple fields
774
    for(int i=0; i<returnFieldList.size(); i++)
775
    {
776
      if(firstfield)
777
      {
778
        firstfield = false;
779
        self.append((String)returnFieldList.elementAt(i));
780
        self.append("' ");
781
      }
782
      else
783
      {
784
        self.append("or xml_index.path like '");
785
        self.append((String)returnFieldList.elementAt(i));
786
        self.append("' ");
787
      }
788
    }
789
    self.append(") AND xml_nodes.docid in (");
790
    //self.append(query.printSQL());
791
    self.append(doclist);
792
    self.append(")");
793
    self.append(" AND xml_nodes.nodetype = 'TEXT'");
794
    
795
    // add control part for extended query
796
    Enumeration en = unaccessableNodePair.keys();
797
    
798
    while (en.hasMoreElements())
799
    {
800
      // Get control pairs in object
801
      Long startNodeIdObject = (Long)en.nextElement();
802
      Long endNodeIdObject = (Long)unaccessableNodePair.get(startNodeIdObject);
803
      // change it to long
804
      long startNodeId = startNodeIdObject.longValue();
805
      long endNodeId   = endNodeIdObject.longValue();
806
      // add into query
807
      self.append(" AND( xml_nodes.nodeid < ");
808
      self.append(startNodeId);
809
      self.append(" OR xml_nodes.nodeid > ");
810
      self.append(endNodeId);
811
      self.append(")");
812
    }
813

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

    
963
    self.append("SELECT docid,docname,doctype,");
964
    self.append("date_created, date_updated ");
965
    self.append("FROM xml_documents WHERE docid IN (");
966
    self.append(docid).append(")");
967
    return self.toString();
968
  }
969
  
970
  /**
971
   * create a String description of the query that this instance represents.
972
   * This should become a way to get the XML serialization of the query.
973
   */
974
  public String toString() {
975
    return "meta_file_id=" + meta_file_id + "\n" + query;
976
//DOCTITLE attr cleared from the db
977
//    return "meta_file_id=" + meta_file_id + "\n" + 
978
//           "querytitle=" + querytitle + "\n" + query;
979
  }
980
  
981
  /* A method to get rid of attribute part in path expression*/
982
  public static String newPathExpressionWithOutAttribute(String pathExpression)
983
  {
984
      if (pathExpression == null)
985
      {
986
        return null;
987
      }
988
      int index = pathExpression.lastIndexOf(ATTRIBUTESYMBOL);
989
      String newExpression = null;
990
      if (index != 1)
991
      {
992
        newExpression=pathExpression.substring(0, index-1);
993
      } 
994
      MetaCatUtil.debugMessage("The path expression without attributes: )" + 
995
                               newExpression, 30);
996
      return newExpression;
997
  }
998
    
999
  /* A method to get attribute name from path */
1000
  public static String getAttributeName(String path)
1001
  {
1002
      if (path == null)
1003
      {
1004
        return null;
1005
      }
1006
      int index = path.lastIndexOf(ATTRIBUTESYMBOL);
1007
      int size = path.length();
1008
      String attributeName = null;
1009
       if (index != 1)
1010
      {
1011
        attributeName = path.substring(index+1, size);
1012
      } 
1013
      MetaCatUtil.debugMessage("The attirbute name from path: )" + 
1014
                               attributeName, 30);
1015
      return attributeName;
1016
  }
1017

    
1018
  /** a utility class that represents a group of terms in a query */
1019
  private class QueryGroup {
1020
    private String operator = null;  // indicates how query terms are combined
1021
    private Vector children = null;  // the list of query terms and groups
1022

    
1023
    /** 
1024
     * construct a new QueryGroup 
1025
     *
1026
     * @param operator the boolean conector used to connect query terms 
1027
     *                    in this query group
1028
     */
1029
    public QueryGroup(String operator) {
1030
      this.operator = operator;
1031
      children = new Vector();
1032
    }
1033

    
1034
    /** 
1035
     * Add a child QueryGroup to this QueryGroup
1036
     *
1037
     * @param qgroup the query group to be added to the list of terms
1038
     */
1039
    public void addChild(QueryGroup qgroup) {
1040
      children.add((Object)qgroup); 
1041
    }
1042

    
1043
    /**
1044
     * Add a child QueryTerm to this QueryGroup
1045
     *
1046
     * @param qterm the query term to be added to the list of terms
1047
     */
1048
    public void addChild(QueryTerm qterm) {
1049
      children.add((Object)qterm); 
1050
    }
1051

    
1052
    /**
1053
     * Retrieve an Enumeration of query terms for this QueryGroup
1054
     */
1055
    public Enumeration getChildren() {
1056
      return children.elements();
1057
    }
1058
   
1059
    /**
1060
     * create a SQL serialization of the query that this instance represents
1061
     */
1062
    public String printSQL(boolean useXMLIndex) {
1063
      StringBuffer self = new StringBuffer();
1064
      boolean first = true;
1065

    
1066
      self.append("(");
1067

    
1068
      Enumeration en= getChildren();
1069
      while (en.hasMoreElements()) {
1070
        Object qobject = en.nextElement();
1071
        if (first) {
1072
          first = false;
1073
        } else {
1074
          self.append(" " + operator + " ");
1075
        }
1076
        if (qobject instanceof QueryGroup) {
1077
          QueryGroup qg = (QueryGroup)qobject;
1078
          self.append(qg.printSQL(useXMLIndex));
1079
        } else if (qobject instanceof QueryTerm) {
1080
          QueryTerm qt = (QueryTerm)qobject;
1081
          self.append(qt.printSQL(useXMLIndex));
1082
        } else {
1083
          System.err.println("qobject wrong type: fatal error");
1084
        }
1085
      }
1086
      self.append(") \n");
1087
      return self.toString();
1088
    }
1089

    
1090
    /**
1091
     * create a String description of the query that this instance represents.
1092
     * This should become a way to get the XML serialization of the query.
1093
     */
1094
    public String toString() {
1095
      StringBuffer self = new StringBuffer();
1096

    
1097
      self.append("  (Query group operator=" + operator + "\n");
1098
      Enumeration en= getChildren();
1099
      while (en.hasMoreElements()) {
1100
        Object qobject = en.nextElement();
1101
        self.append(qobject);
1102
      }
1103
      self.append("  )\n");
1104
      return self.toString();
1105
    }
1106
  }
1107

    
1108
  /** a utility class that represents a single term in a query */
1109
  private class QueryTerm {
1110
    private boolean casesensitive = false;
1111
    private String searchmode = null;
1112
    private String value = null;
1113
    private String pathexpr = null;
1114
    private boolean percentageSymbol = false;
1115
   
1116

    
1117
    /**
1118
     * Construct a new instance of a query term for a free text search
1119
     * (using the value only)
1120
     *
1121
     * @param casesensitive flag indicating whether case is used to match
1122
     * @param searchmode determines what kind of substring match is performed
1123
     *        (one of starts-with|ends-with|contains|matches-exactly)
1124
     * @param value the text value to match
1125
     */
1126
    public QueryTerm(boolean casesensitive, String searchmode, 
1127
                     String value) {
1128
      this.casesensitive = casesensitive;
1129
      this.searchmode = searchmode;
1130
      this.value = value;
1131
    }
1132

    
1133
    /**
1134
     * Construct a new instance of a query term for a structured search
1135
     * (matching the value only for those nodes in the pathexpr)
1136
     *
1137
     * @param casesensitive flag indicating whether case is used to match
1138
     * @param searchmode determines what kind of substring match is performed
1139
     *        (one of starts-with|ends-with|contains|matches-exactly)
1140
     * @param value the text value to match
1141
     * @param pathexpr the hierarchical path to the nodes to be searched
1142
     */
1143
    public QueryTerm(boolean casesensitive, String searchmode, 
1144
                     String value, String pathexpr) {
1145
      this(casesensitive, searchmode, value);
1146
      this.pathexpr = pathexpr;
1147
    }
1148

    
1149
    /** determine if the QueryTerm is case sensitive */
1150
    public boolean isCaseSensitive() {
1151
      return casesensitive;
1152
    }
1153

    
1154
    /** get the searchmode parameter */
1155
    public String getSearchMode() {
1156
      return searchmode;
1157
    }
1158
 
1159
    /** get the Value parameter */
1160
    public String getValue() {
1161
      return value;
1162
    }
1163

    
1164
    /** get the path expression parameter */
1165
    public String getPathExpression() {
1166
      return pathexpr;
1167
    }
1168

    
1169
    /**
1170
     * create a SQL serialization of the query that this instance represents
1171
     */
1172
    public String printSQL(boolean useXMLIndex) {
1173
      StringBuffer self = new StringBuffer();
1174

    
1175
      // Uppercase the search string if case match is not important
1176
      String casevalue = null;
1177
      String nodedataterm = null;
1178

    
1179
      if (casesensitive) {
1180
        nodedataterm = "nodedata";
1181
        casevalue = value;
1182
      } else {
1183
        nodedataterm = "UPPER(nodedata)";
1184
        casevalue = value.toUpperCase();
1185
      }
1186

    
1187
      // Add appropriate wildcards to search string
1188
      //String searchvalue = null;
1189
      String searchexpr = null;
1190
      if (searchmode.equals("starts-with")) {
1191
        //searchvalue = casevalue + "%";
1192
        searchexpr = nodedataterm + " LIKE '" + casevalue + "%' ";
1193
      } else if (searchmode.equals("ends-with")) {
1194
        //searchvalue = "%" + casevalue;
1195
        searchexpr = nodedataterm + " LIKE '%" + casevalue + "' ";
1196
      } else if (searchmode.equals("contains")) {
1197
        //searchvalue = "%" + casevalue + "%";
1198
        if (!casevalue.equals("%"))
1199
        {
1200
          searchexpr = nodedataterm + " LIKE '%" + casevalue + "%' ";
1201
        }
1202
        else
1203
        {
1204
          searchexpr = nodedataterm + " LIKE '" + casevalue + "' ";
1205
          // find percentage symbol
1206
          percentageSymbol = true;
1207
        }
1208
      } else if (searchmode.equals("equals")) {
1209
        //searchvalue = casevalue;
1210
        searchexpr = nodedataterm + " = '" + casevalue + "' ";
1211
      } else if (searchmode.equals("isnot-equal")) {
1212
        //searchvalue = casevalue;
1213
        searchexpr = nodedataterm + " != '" + casevalue + "' ";
1214
      } else { 
1215
        //searchvalue = casevalue;
1216
        String oper = null;
1217
        if (searchmode.equals("greater-than")) {
1218
          oper = ">";
1219
        } else if (searchmode.equals("greater-than-equals")) {
1220
          oper = ">=";
1221
        } else if (searchmode.equals("less-than")) {
1222
          oper = "<";
1223
        } else if (searchmode.equals("less-than-equals")) {
1224
          oper = "<=";
1225
        } else {
1226
          System.out.println("NOT expected case. NOT recognized operator: " +
1227
                             searchmode);
1228
          return null;
1229
        }
1230
        try {
1231
          // it is number; numeric comparison
1232
          // but we need to make sure there is no string in node data
1233
          String getRidOfString = " AND UPPER(nodedata) = LOWER(nodedata)" +
1234
                                  " AND LTRIM(nodedata) != ' ' " +
1235
                                  " AND nodedata IS NOT NULL ";
1236
          searchexpr = nodedataterm + " " + oper + " " +
1237
                       new Double(casevalue) + " "+getRidOfString;          
1238
        } catch (NumberFormatException nfe) {
1239
          // these are characters; character comparison
1240
          searchexpr = nodedataterm + " " + oper + " '" + casevalue + "' ";
1241
        }
1242
      }
1243

    
1244
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
1245
      //self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
1246
      self.append(searchexpr);
1247

    
1248
      if (pathexpr != null) 
1249
      {
1250
        
1251
        // use XML Index
1252
        if ( useXMLIndex ) 
1253
        {
1254
          if (!hasAttributeInPath(pathexpr))
1255
          {
1256
            // without attributes in path
1257
            self.append("AND parentnodeid IN ");
1258
          }
1259
          else
1260
          {
1261
            // has a attribute in path
1262
            String attributeName = getAttributeName(pathexpr);
1263
            self.append("AND nodetype LIKE 'ATTRIBUTE' AND nodename LIKE '"+
1264
                        attributeName + "' ");
1265
            self.append("AND parentnodeid IN ");
1266
            pathexpr = newPathExpressionWithOutAttribute(pathexpr);
1267
            
1268
          } 
1269
          self.append("(SELECT nodeid FROM xml_index WHERE path LIKE " + 
1270
                      "'" +  pathexpr + "') " );
1271
        } 
1272
        else 
1273
        {
1274
          // without using XML Index; using nested statements instead
1275
          self.append("AND parentnodeid IN ");
1276
          self.append(useNestedStatements(pathexpr));
1277
        }
1278
      }
1279
      else
1280
      {
1281
        //if pathexpr is null and search value is %, is a percentageSearchItem
1282
        // the count number will be increase one
1283
        countPercentageSearchItem++;
1284
        
1285
       }
1286

    
1287
      return self.toString();
1288
    }
1289
    
1290
    /* A method to judge if a path have attribute */
1291
    private boolean hasAttributeInPath(String path)
1292
    {
1293
      if (path.indexOf(ATTRIBUTESYMBOL)!=-1)
1294
      {
1295
        return true;
1296
      }
1297
      else
1298
      {
1299
        return false;
1300
      }
1301
    }
1302
    
1303
   
1304
    
1305
    /* 
1306
     * Constraint the query with @pathexp without using the XML Index,
1307
     * but nested SQL statements instead. The query migth be slower.
1308
     */
1309
    private String useNestedStatements(String pathexpr)
1310
    {
1311
      StringBuffer nestedStmts = new StringBuffer();
1312
      Vector nodes = new Vector();
1313
      String path = pathexpr;
1314
      int inx = 0;
1315

    
1316
      do {
1317
        inx = path.lastIndexOf("/");
1318

    
1319
        nodes.addElement(path.substring(inx+1));
1320
        path = path.substring(0, Math.abs(inx));
1321
      } while ( inx > 0 );
1322
      
1323
      // nested statements
1324
      int i = 0;
1325
      for (i = 0; i < nodes.size()-1; i++) {
1326
        nestedStmts.append("(SELECT nodeid FROM xml_nodes" + 
1327
                           " WHERE nodename LIKE '" +
1328
                             (String)nodes.elementAt(i) + "'" +
1329
                           " AND parentnodeid IN ");
1330
      }
1331
      // for the last statement: it is without " AND parentnodeid IN "
1332
      nestedStmts.append("(SELECT nodeid FROM xml_nodes" + 
1333
                         " WHERE nodename LIKE '" +
1334
                         (String)nodes.elementAt(i) + "'" );
1335
      // node.size() number of closing brackets
1336
      for (i = 0; i < nodes.size(); i++) {
1337
        nestedStmts.append(")");
1338
      }
1339

    
1340

    
1341

    
1342
      return nestedStmts.toString();
1343
    }
1344

    
1345
    /**
1346
     * create a String description of the query that this instance represents.
1347
     * This should become a way to get the XML serialization of the query.
1348
     */
1349
    public String toString() {
1350

    
1351
      return this.printSQL(true);
1352
    }
1353
  }
1354
}
(45-45/54)