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-05 10:18:02 -0800 (Wed, 05 Mar 2003) $'
14
 * '$Revision: 1454 $'
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 ";
178
    if (userName != null && !userName.equals(""))
179
    {
180
      ownerQuery = ownerQuery + "user_owner ='"+ userName +"'";
181
    }
182
    
183
    MetaCatUtil.debugMessage("OwnerQuery: "+ownerQuery, 30);
184
    return ownerQuery;
185
  }
186
  
187
  /*
188
   * Method to create query for xml_access, this part is to get docid list which
189
   * have a allow rule for a given user
190
   */
191
  private String createAllowRuleQuery()
192
  {
193
    String allowQuery = null;
194
    String allowString = constructAllowString();
195
    allowQuery ="SELECT docid from xml_access WHERE( "+allowString;
196
    allowQuery = allowQuery +") AND subtreeid IS NULL";
197
    MetaCatUtil.debugMessage("allow query is: "+ allowQuery, 30);
198
    return allowQuery;
199
    
200
  
201
  }
202
  
203
  /* Method to construct a allow rule string */
204
  private String constructAllowString()
205
  {
206
    String allowQuery ="";
207
     // add allow rule for user name
208
     if (userName != null && !userName.equals(""))
209
    {
210
      allowQuery = allowQuery +"(principal_name = '" + userName 
211
                              +"' AND perm_type = 'allow'"
212
                              +" AND (permission='4' OR permission='7'))";
213
    }
214
    // add allow rule for public
215
    allowQuery = allowQuery +"OR (principal_name = '" + PUBLIC 
216
                              +"' AND perm_type = 'allow'"
217
                              +" AND (permission='4' OR permission='7'))";
218
    
219
    // add allow rule for group
220
    if (group != null)
221
    {
222
      for (int i = 0; i< group.length; i++)
223
      {
224
        String groupUint = group[i];
225
        if (groupUint != null && !groupUint.equals(""))
226
        {
227
          allowQuery = allowQuery +" OR (principal_name = '" + groupUint 
228
                              +"' AND perm_type = 'allow'"
229
                              +" AND (permission='4' OR permission='7'))";
230
        }//if
231
      }//for
232
    }//if
233
    MetaCatUtil.debugMessage("allow string is: "+ allowQuery, 40);
234
    return allowQuery;
235
  }
236

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

    
311
     if (args.length < 1) {
312
       System.err.println("Wrong number of arguments!!!");
313
       System.err.println("USAGE: java QuerySpecification <xmlfile>");
314
       return;
315
     } else {
316
       int i = 0;
317
       boolean useXMLIndex = true;
318
       if ( args[i].equals( "-noindex" ) ) {
319
         useXMLIndex = false;
320
         i++;
321
       }
322
       String xmlfile  = args[i];
323

    
324
       try {
325
         MetaCatUtil util = new MetaCatUtil();
326
         FileReader xml = new FileReader(new File(xmlfile));
327
         QuerySpecification qspec = 
328
                 new QuerySpecification(xml, util.getOption("saxparser"),
329
                                        util.getOption("accNumberSeparator"));
330
         System.out.println(qspec.printSQL(useXMLIndex));
331

    
332
       } catch (IOException e) {
333
         System.err.println(e.getMessage());
334
       }
335
         
336
     }
337
  }
338
  
339
  /**
340
   * Returns true if the parsed query contains and extended xml query 
341
   * (i.e. there is at least one &lt;returnfield&gt; in the pathquery document)
342
   */
343
  public boolean containsExtendedSQL()
344
  {
345
    if(containsExtendedSQL)
346
    {
347
      return true;
348
    }
349
    else
350
    {
351
      return false;
352
    }
353
  }
354
  
355
  /**
356
   * A method to get if the query has an attribute return field
357
   */
358
  public boolean containAttributeReturnField()
359
  {
360
    return hasAttributeReturnField;
361
  }
362
  
363
  /**
364
   * Accessor method to return the identifier of this Query
365
   */
366
  public String getIdentifier()
367
  {
368
    return meta_file_id;
369
  }
370

    
371
  /**
372
   * method to set the identifier of this query
373
   */
374
  public void setIdentifier(String id) {
375
    this.meta_file_id = id;
376
  }
377

    
378
  /**
379
   * Accessor method to return the title of this Query
380
   */
381
  public String getQueryTitle()
382
  {
383
    return queryTitle;
384
  }
385

    
386
  /**
387
   * method to set the title of this query
388
   */
389
  public void setQueryTitle(String title)
390
  {
391
    this.queryTitle = title;
392
  }
393

    
394
  /**
395
   * Accessor method to return a vector of the return document types as
396
   * defined in the &lt;returndoctype&gt; tag in the pathquery dtd.
397
   */
398
  public Vector getReturnDocList()
399
  {
400
    return this.returnDocList;
401
  }
402

    
403
  /**
404
   * method to set the list of return docs of this query
405
   */
406
  public void setReturnDocList(Vector returnDocList)
407
  {
408
    this.returnDocList = returnDocList;
409
  }
410

    
411
  /**
412
   * Accessor method to return a vector of the filter doc types as
413
   * defined in the &lt;filterdoctype&gt; tag in the pathquery dtd.
414
   */
415
  public Vector getFilterDocList()
416
  {
417
    return this.filterDocList;
418
  }
419

    
420
  /**
421
   * method to set the list of filter docs of this query
422
   */
423
  public void setFilterDocList(Vector filterDocList)
424
  {
425
    this.filterDocList = filterDocList;
426
  }
427

    
428
  /**
429
   * Accessor method to return a vector of the extended return fields as
430
   * defined in the &lt;returnfield&gt; tag in the pathquery dtd.
431
   */
432
  public Vector getReturnFieldList()
433
  {
434
    return this.returnFieldList; 
435
  }
436

    
437
  /**
438
   * method to set the list of fields to be returned by this query
439
   */
440
  public void setReturnFieldList(Vector returnFieldList)
441
  {
442
    this.returnFieldList = returnFieldList;
443
  }
444

    
445
  /**
446
   * Accessor method to return a vector of the owner fields as
447
   * defined in the &lt;owner&gt; tag in the pathquery dtd.
448
   */
449
  public Vector getOwnerList()
450
  {
451
    return this.ownerList;
452
  }
453

    
454
  /**
455
   * method to set the list of owners used to constrain this query
456
   */
457
  public void setOwnerList(Vector ownerList)
458
  {
459
    this.ownerList = ownerList;
460
  }
461

    
462
  /**
463
   * Accessor method to return a vector of the site fields as
464
   * defined in the &lt;site&gt; tag in the pathquery dtd.
465
   */
466
  public Vector getSiteList()
467
  {
468
    return this.siteList;
469
  }
470

    
471
  /**
472
   * method to set the list of sites used to constrain this query
473
   */
474
  public void setSiteList(Vector siteList)
475
  {
476
    this.siteList = siteList;
477
  }
478

    
479
  /**
480
   * get the QueryGroup used to express query constraints
481
   */
482
  public QueryGroup getQueryGroup()
483
  {
484
    return query;
485
  }
486

    
487
  /**
488
   * Set up the SAX parser for reading the XML serialized query
489
   */
490
  private XMLReader initializeParser() {
491
    XMLReader parser = null;
492

    
493
    // Set up the SAX document handlers for parsing
494
    try {
495

    
496
      // Get an instance of the parser
497
      parser = XMLReaderFactory.createXMLReader(parserName);
498

    
499
      // Set the ContentHandler to this instance
500
      parser.setContentHandler(this);
501

    
502
      // Set the error Handler to this instance
503
      parser.setErrorHandler(this);
504

    
505
    } catch (Exception e) {
506
       System.err.println("Error in QuerySpcecification.initializeParser " + 
507
                           e.toString());
508
    }
509

    
510
    return parser;
511
  }
512

    
513
  /**
514
   * callback method used by the SAX Parser when the start tag of an 
515
   * element is detected. Used in this context to parse and store
516
   * the query information in class variables.
517
   */
518
  public void startElement (String uri, String localName, 
519
                            String qName, Attributes atts) 
520
         throws SAXException {
521
    BasicNode currentNode = new BasicNode(localName);
522
    // add attributes to BasicNode here
523
    if (atts != null) {
524
      int len = atts.getLength();
525
      for (int i = 0; i < len; i++) {
526
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
527
      }
528
    }
529

    
530
    elementStack.push(currentNode); 
531
    if (currentNode.getTagName().equals("querygroup")) {
532
      QueryGroup currentGroup = new QueryGroup(
533
                                currentNode.getAttribute("operator"));
534
      if (query == null) {
535
        query = currentGroup;
536
      } else {
537
        QueryGroup parentGroup = (QueryGroup)queryStack.peek();
538
        parentGroup.addChild(currentGroup);
539
      }
540
      queryStack.push(currentGroup);
541
    }
542
  }
543

    
544
  /**
545
   * callback method used by the SAX Parser when the end tag of an 
546
   * element is detected. Used in this context to parse and store
547
   * the query information in class variables.
548
   */
549
  public void endElement (String uri, String localName,
550
                          String qName) throws SAXException {
551
    BasicNode leaving = (BasicNode)elementStack.pop(); 
552
    if (leaving.getTagName().equals("queryterm")) {
553
      boolean isCaseSensitive = (new Boolean(
554
              leaving.getAttribute("casesensitive"))).booleanValue();
555
      QueryTerm currentTerm = null;
556
      if (currentPathexpr == null) {
557
        currentTerm = new QueryTerm(isCaseSensitive,
558
                      leaving.getAttribute("searchmode"),currentValue);
559
      } else {
560
        currentTerm = new QueryTerm(isCaseSensitive,
561
                      leaving.getAttribute("searchmode"),currentValue,
562
                      currentPathexpr);
563
      }
564
      QueryGroup currentGroup = (QueryGroup)queryStack.peek();
565
      currentGroup.addChild(currentTerm);
566
      currentValue = null;
567
      currentPathexpr = null;
568
    } else if (leaving.getTagName().equals("querygroup")) {
569
      QueryGroup leavingGroup = (QueryGroup)queryStack.pop();
570
    }
571
  }
572

    
573
  /**
574
   * callback method used by the SAX Parser when the text sequences of an 
575
   * xml stream are detected. Used in this context to parse and store
576
   * the query information in class variables.
577
   */
578
  public void characters(char ch[], int start, int length) {
579

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

    
632
  /**
633
   * create a SQL serialization of the query that this instance represents
634
   */
635
  public String printSQL(boolean useXMLIndex) {
636
    
637
   
638
    StringBuffer self = new StringBuffer();
639

    
640
    self.append("SELECT docid,docname,doctype,");
641
    self.append("date_created, date_updated, rev ");
642
    self.append("FROM xml_documents WHERE docid IN (");
643

    
644
    // This determines the documents that meet the query conditions
645
    self.append(query.printSQL(useXMLIndex));
646

    
647
    self.append(") ");
648
 
649
    // Add SQL to filter for doctypes requested in the query
650
    // This is an implicit OR for the list of doctypes. Only doctypes in this
651
    // list will be searched if the tag is present
652
    if (!filterDocList.isEmpty()) {
653
      boolean firstdoctype = true;
654
      self.append(" AND ("); 
655
      Enumeration en = filterDocList.elements();
656
      while (en.hasMoreElements()) {
657
        String currentDoctype = (String)en.nextElement();
658
        if (firstdoctype) {
659
           firstdoctype = false;
660
           self.append(" doctype = '" + currentDoctype + "'"); 
661
        } else {
662
          self.append(" OR doctype = '" + currentDoctype + "'"); 
663
        }
664
      }
665
      self.append(") ");
666
    }
667

    
668
    // Add SQL to filter for owners requested in the query
669
    // This is an implicit OR for the list of owners
670
    if (!ownerList.isEmpty()) {
671
      boolean first = true;
672
      self.append(" AND ("); 
673
      Enumeration en = ownerList.elements();
674
      while (en.hasMoreElements()) {
675
        String current = (String)en.nextElement();
676
        if (first) {
677
           first = false;
678
           self.append(" user_owner = '" + current + "'"); 
679
        } else {
680
          self.append(" OR user_owner = '" + current + "'"); 
681
        }
682
      }
683
      self.append(") ");
684
    }
685

    
686
    // Add SQL to filter for sites requested in the query
687
    // This is an implicit OR for the list of sites
688
    if (!siteList.isEmpty()) {
689
      boolean first = true;
690
      self.append(" AND ("); 
691
      Enumeration en = siteList.elements();
692
      while (en.hasMoreElements()) {
693
        String current = (String)en.nextElement();
694
        if (first) {
695
           first = false;
696
           self.append(" SUBSTR(docid, 1, INSTR(docid, '" +
697
               accNumberSeparator + "')-1) = '" + current + "'"); 
698
        } else {
699
          self.append(" OR SUBSTR(docid, 1, INSTR(docid, '" +
700
               accNumberSeparator + "')-1) = '" + current + "'"); 
701
        }
702
      }
703
      self.append(") ");
704
    }
705
    
706
    // if there is only one percentage search item, this query is a percentage
707
    // search query
708
    if (countPercentageSearchItem ==1)
709
    {
710
      
711
      percentageSearch =true;
712
    }
713
    
714
    return self.toString();
715
  }
716
  
717
  /** This sql command will selecet startnodeid and endnodeid that user can NOT
718
    * access
719
    */
720
  public String printAccessControlSQLForReturnField(String doclist)
721
  {
722
    StringBuffer sql = new StringBuffer();
723
    String allowString = constructAllowString();
724
    String denyString = constructDenyString();
725
    sql.append("SELECT distinct startnodeid, endnodeid from xml_access ");
726
    sql.append("WHERE docid in (");
727
    sql.append(doclist);
728
    sql.append(") AND startnodeid IS NOT NULL AND ");
729
    sql.append("(");
730
    sql.append("(");
731
    sql.append("startnodeid NOT IN (SELECT startnodeid from xml_access, xml_documents ");
732
    sql.append(" WHERE xml_access.docid = xml_documents.docid");
733
    sql.append(" AND xml_documents.user_owner ='");
734
    sql.append(userName);
735
    sql.append("' AND xml_access.startnodeid IS NOT NULL)"); 
736
    sql.append(")");
737
    sql.append(" AND ");
738
    sql.append("(");
739
    sql.append("(startnodeid NOT IN (SELECT startnodeid from xml_access where( ");
740
    sql.append(allowString);
741
    sql.append(") AND (startnodeid IS NOT NULL))");
742
    sql.append(")");
743
    sql.append(" OR (startnodeid IN (SELECT startnodeid from xml_access where( ");
744
    sql.append(denyString);
745
    sql.append(") AND (startnodeid IS NOT NULL))");
746
    sql.append(")");
747
    sql.append(")");
748
    sql.append(")");
749
    MetaCatUtil.debugMessage("accessControlSQLForReturnField: " +
750
                             sql.toString(), 30);
751
    return sql.toString();
752
  }
753
  
754
  /**
755
   * This method prints sql based upon the &lt;returnfield&gt; tag in the
756
   * pathquery document.  This allows for customization of the 
757
   * returned fields
758
   * @param doclist the list of document ids to search by
759
   * @param unaccessableNodePair  the node pair(start id and end id) which this
760
   *                               user could not access it
761
   */
762
  public String printExtendedSQL(String doclist, Hashtable unaccessableNodePair)
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
    // add control part for extended query
794
    Enumeration en = unaccessableNodePair.keys();
795
    
796
    while (en.hasMoreElements())
797
    {
798
      // Get control pairs in object
799
      Long startNodeIdObject = (Long)en.nextElement();
800
      Long endNodeIdObject = (Long)unaccessableNodePair.get(startNodeIdObject);
801
      // change it to long
802
      long startNodeId = startNodeIdObject.longValue();
803
      long endNodeId   = endNodeIdObject.longValue();
804
      // add into query
805
      self.append(" AND( xml_nodes.nodeid < ");
806
      self.append(startNodeId);
807
      self.append(" OR xml_nodes.nodeid > ");
808
      self.append(endNodeId);
809
      self.append(")");
810
    }
811

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

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

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

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

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

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

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

    
1064
      self.append("(");
1065

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1314
      do {
1315
        inx = path.lastIndexOf("/");
1316

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

    
1338

    
1339

    
1340
      return nestedStmts.toString();
1341
    }
1342

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

    
1349
      return this.printSQL(true);
1350
    }
1351
  }
1352
}
(48-48/57)