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: 2002-11-21 11:40:37 -0800 (Thu, 21 Nov 2002) $'
14
 * '$Revision: 1332 $'
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.Stack;
37
import java.util.Vector;
38
import java.util.Enumeration;
39

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

    
48
/**
49
 * A Class that represents a structured query, and can be 
50
 * constructed from an XML serialization conforming to @see pathquery.dtd. 
51
 * The printSQL() method can be used to print a SQL serialization of the query.
52
 */
53
public class QuerySpecification extends DefaultHandler {
54
 
55
  /** flag determining whether extended query terms are present */
56
  private boolean containsExtendedSQL=false;
57
  /** Identifier for this query document */
58
  private String meta_file_id;
59
  /** Title of this query */
60
  private String queryTitle;
61
  /** List of document types to be returned using package back tracing */
62
  private Vector returnDocList;
63
  /** List of document types to be searched */
64
  private Vector filterDocList;
65
  /** List of fields to be returned in result set */
66
  private Vector returnFieldList;
67
  /** List of users owning documents to be searched */
68
  private Vector ownerList;
69
  /** List of sites/scopes used to constrain search */
70
  private Vector siteList;
71
  /** The root query group that contains the recursive query constraints */
72
  private QueryGroup query = null;
73

    
74
  // Query data structures used temporarily during XML parsing
75
  private Stack elementStack;
76
  private Stack queryStack;
77
  private String currentValue;
78
  private String currentPathexpr;
79
  private String parserName = null;
80
  private String accNumberSeparator = null;
81
  private static final AbstractDatabase dbAdapter = MetaCatUtil.dbAdapter;
82
  
83
  private String userName = null;
84
  private static final String PUBLIC = "public";
85
  private String [] group = null;
86

    
87

    
88
  /**
89
   * construct an instance of the QuerySpecification class 
90
   *
91
   * @param queryspec the XML representation of the query (should conform
92
   *                  to pathquery.dtd) as a Reader
93
   * @param parserName the fully qualified name of a Java Class implementing
94
   *                  the org.xml.sax.XMLReader interface
95
   */
96
  public QuerySpecification( Reader queryspec, String parserName,
97
         String accNumberSeparator ) throws IOException {
98
    super();
99
    
100
    // Initialize the class variables
101
    returnDocList = new Vector();
102
    filterDocList = new Vector();
103
    elementStack = new Stack();
104
    queryStack   = new Stack();
105
    returnFieldList = new Vector();
106
    ownerList = new Vector();
107
    siteList = new Vector();
108
    this.parserName = parserName;
109
    this.accNumberSeparator = accNumberSeparator;
110

    
111
    // Initialize the parser and read the queryspec
112
    XMLReader parser = initializeParser();
113
    if (parser == null) {
114
      System.err.println("SAX parser not instantiated properly.");
115
    }
116
    try {
117
      parser.parse(new InputSource(queryspec));
118
    } catch (SAXException e) {
119
      System.err.println("error parsing data in " + 
120
                         "QuerySpecification.QuerySpecification");
121
      System.err.println(e.getMessage());
122
    }
123
  }
124

    
125
  /**
126
   * construct an instance of the QuerySpecification class 
127
   *
128
   * @param queryspec the XML representation of the query (should conform
129
   *                  to pathquery.dtd) as a String
130
   * @param parserName the fully qualified name of a Java Class implementing
131
   *                  the org.xml.sax.Parser interface
132
   */
133
  public QuerySpecification( String queryspec, String parserName,
134
         String accNumberSeparator) throws IOException {
135
    this(new StringReader(queryspec), parserName, accNumberSeparator);
136
  }
137
  
138
  /**
139
   * Method to set user name
140
   *
141
   * @param myName  the user name
142
   */
143
  public void setUserName(String myName)
144
  {
145
    this.userName = myName;
146
  }
147
  
148
  /**
149
   * Method to set user group
150
   *
151
   * @param myGroup  the user group
152
   */
153
  public void setGroup(String [] myGroup)
154
  {
155
    this.group = myGroup;
156
  }
157
  
158
  /*
159
   * Method to get owner query. If it is owner it has all permission
160
   */
161
  private String createOwerQuery()
162
  {
163
    String ownerQuery = null;
164
    ownerQuery = "SELECT docid FROM xml_documents WHERE user_owner ='" +
165
                  PUBLIC + "'";
166
    if (userName != null && !userName.equals(""))
167
    {
168
      ownerQuery = ownerQuery + " OR user_owner ='"+ userName +"'";
169
    }
170
    
171
    if (group != null)
172
    {
173
      for (int i = 0; i< group.length; i++)
174
      {
175
        String groupUint = group[i];
176
        if (groupUint != null && !groupUint.equals(""))
177
        {
178
          ownerQuery = ownerQuery +" OR user_owner = '" + groupUint + "'";
179
        }//if
180
      }//for
181
    }
182
    MetaCatUtil.debugMessage("OwnerQuery: "+ownerQuery, 30);
183
    return ownerQuery;
184
  }
185
  
186
  /*
187
   * Method to create query for xml_access, this part is to get docid list which
188
   * have a allow rule for a given user
189
   */
190
  private String createAllowRuleQuery()
191
  {
192
    String allowQuery = null;
193
    allowQuery ="SELECT docid from xml_access WHERE ";
194
    // add allow rule for user name
195
    if (userName != null && !userName.equals(""))
196
    {
197
      allowQuery = allowQuery +"(principal_name = '" + userName 
198
                              +"' AND perm_type = 'allow'"
199
                              +" AND (permission='4' OR permission='7'))";
200
    }
201
    // add allow rule for public
202
    allowQuery = allowQuery +"OR (principal_name = '" + PUBLIC 
203
                              +"' AND perm_type = 'allow'"
204
                              +" AND (permission='4' OR permission='7'))";
205
    
206
    // add allow rule for group
207
    if (group != null)
208
    {
209
      for (int i = 0; i< group.length; i++)
210
      {
211
        String groupUint = group[i];
212
        if (groupUint != null && !groupUint.equals(""))
213
        {
214
          allowQuery = allowQuery +" OR (principal_name = '" + groupUint 
215
                              +"' AND perm_type = 'allow'"
216
                              +" AND (permission='4' OR permission='7'))";
217
        }//if
218
      }//for
219
    }//if
220
    MetaCatUtil.debugMessage("allow query is: "+ allowQuery, 30);
221
    return allowQuery;
222
  
223
  }
224

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

    
291
     if (args.length < 1) {
292
       System.err.println("Wrong number of arguments!!!");
293
       System.err.println("USAGE: java QuerySpecification <xmlfile>");
294
       return;
295
     } else {
296
       int i = 0;
297
       boolean useXMLIndex = true;
298
       if ( args[i].equals( "-noindex" ) ) {
299
         useXMLIndex = false;
300
         i++;
301
       }
302
       String xmlfile  = args[i];
303

    
304
       try {
305
         MetaCatUtil util = new MetaCatUtil();
306
         FileReader xml = new FileReader(new File(xmlfile));
307
         QuerySpecification qspec = 
308
                 new QuerySpecification(xml, util.getOption("saxparser"),
309
                                        util.getOption("accNumberSeparator"));
310
         System.out.println(qspec.printSQL(useXMLIndex));
311

    
312
       } catch (IOException e) {
313
         System.err.println(e.getMessage());
314
       }
315
         
316
     }
317
  }
318
  
319
  /**
320
   * Returns true if the parsed query contains and extended xml query 
321
   * (i.e. there is at least one &lt;returnfield&gt; in the pathquery document)
322
   */
323
  public boolean containsExtendedSQL()
324
  {
325
    if(containsExtendedSQL)
326
    {
327
      return true;
328
    }
329
    else
330
    {
331
      return false;
332
    }
333
  }
334
  
335
  /**
336
   * Accessor method to return the identifier of this Query
337
   */
338
  public String getIdentifier()
339
  {
340
    return meta_file_id;
341
  }
342

    
343
  /**
344
   * method to set the identifier of this query
345
   */
346
  public void setIdentifier(String id) {
347
    this.meta_file_id = id;
348
  }
349

    
350
  /**
351
   * Accessor method to return the title of this Query
352
   */
353
  public String getQueryTitle()
354
  {
355
    return queryTitle;
356
  }
357

    
358
  /**
359
   * method to set the title of this query
360
   */
361
  public void setQueryTitle(String title)
362
  {
363
    this.queryTitle = title;
364
  }
365

    
366
  /**
367
   * Accessor method to return a vector of the return document types as
368
   * defined in the &lt;returndoctype&gt; tag in the pathquery dtd.
369
   */
370
  public Vector getReturnDocList()
371
  {
372
    return this.returnDocList;
373
  }
374

    
375
  /**
376
   * method to set the list of return docs of this query
377
   */
378
  public void setReturnDocList(Vector returnDocList)
379
  {
380
    this.returnDocList = returnDocList;
381
  }
382

    
383
  /**
384
   * Accessor method to return a vector of the filter doc types as
385
   * defined in the &lt;filterdoctype&gt; tag in the pathquery dtd.
386
   */
387
  public Vector getFilterDocList()
388
  {
389
    return this.filterDocList;
390
  }
391

    
392
  /**
393
   * method to set the list of filter docs of this query
394
   */
395
  public void setFilterDocList(Vector filterDocList)
396
  {
397
    this.filterDocList = filterDocList;
398
  }
399

    
400
  /**
401
   * Accessor method to return a vector of the extended return fields as
402
   * defined in the &lt;returnfield&gt; tag in the pathquery dtd.
403
   */
404
  public Vector getReturnFieldList()
405
  {
406
    return this.returnFieldList; 
407
  }
408

    
409
  /**
410
   * method to set the list of fields to be returned by this query
411
   */
412
  public void setReturnFieldList(Vector returnFieldList)
413
  {
414
    this.returnFieldList = returnFieldList;
415
  }
416

    
417
  /**
418
   * Accessor method to return a vector of the owner fields as
419
   * defined in the &lt;owner&gt; tag in the pathquery dtd.
420
   */
421
  public Vector getOwnerList()
422
  {
423
    return this.ownerList;
424
  }
425

    
426
  /**
427
   * method to set the list of owners used to constrain this query
428
   */
429
  public void setOwnerList(Vector ownerList)
430
  {
431
    this.ownerList = ownerList;
432
  }
433

    
434
  /**
435
   * Accessor method to return a vector of the site fields as
436
   * defined in the &lt;site&gt; tag in the pathquery dtd.
437
   */
438
  public Vector getSiteList()
439
  {
440
    return this.siteList;
441
  }
442

    
443
  /**
444
   * method to set the list of sites used to constrain this query
445
   */
446
  public void setSiteList(Vector siteList)
447
  {
448
    this.siteList = siteList;
449
  }
450

    
451
  /**
452
   * get the QueryGroup used to express query constraints
453
   */
454
  public QueryGroup getQueryGroup()
455
  {
456
    return query;
457
  }
458

    
459
  /**
460
   * Set up the SAX parser for reading the XML serialized query
461
   */
462
  private XMLReader initializeParser() {
463
    XMLReader parser = null;
464

    
465
    // Set up the SAX document handlers for parsing
466
    try {
467

    
468
      // Get an instance of the parser
469
      parser = XMLReaderFactory.createXMLReader(parserName);
470

    
471
      // Set the ContentHandler to this instance
472
      parser.setContentHandler(this);
473

    
474
      // Set the error Handler to this instance
475
      parser.setErrorHandler(this);
476

    
477
    } catch (Exception e) {
478
       System.err.println("Error in QuerySpcecification.initializeParser " + 
479
                           e.toString());
480
    }
481

    
482
    return parser;
483
  }
484

    
485
  /**
486
   * callback method used by the SAX Parser when the start tag of an 
487
   * element is detected. Used in this context to parse and store
488
   * the query information in class variables.
489
   */
490
  public void startElement (String uri, String localName, 
491
                            String qName, Attributes atts) 
492
         throws SAXException {
493
    BasicNode currentNode = new BasicNode(localName);
494
    // add attributes to BasicNode here
495
    if (atts != null) {
496
      int len = atts.getLength();
497
      for (int i = 0; i < len; i++) {
498
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
499
      }
500
    }
501

    
502
    elementStack.push(currentNode); 
503
    if (currentNode.getTagName().equals("querygroup")) {
504
      QueryGroup currentGroup = new QueryGroup(
505
                                currentNode.getAttribute("operator"));
506
      if (query == null) {
507
        query = currentGroup;
508
      } else {
509
        QueryGroup parentGroup = (QueryGroup)queryStack.peek();
510
        parentGroup.addChild(currentGroup);
511
      }
512
      queryStack.push(currentGroup);
513
    }
514
  }
515

    
516
  /**
517
   * callback method used by the SAX Parser when the end tag of an 
518
   * element is detected. Used in this context to parse and store
519
   * the query information in class variables.
520
   */
521
  public void endElement (String uri, String localName,
522
                          String qName) throws SAXException {
523
    BasicNode leaving = (BasicNode)elementStack.pop(); 
524
    if (leaving.getTagName().equals("queryterm")) {
525
      boolean isCaseSensitive = (new Boolean(
526
              leaving.getAttribute("casesensitive"))).booleanValue();
527
      QueryTerm currentTerm = null;
528
      if (currentPathexpr == null) {
529
        currentTerm = new QueryTerm(isCaseSensitive,
530
                      leaving.getAttribute("searchmode"),currentValue);
531
      } else {
532
        currentTerm = new QueryTerm(isCaseSensitive,
533
                      leaving.getAttribute("searchmode"),currentValue,
534
                      currentPathexpr);
535
      }
536
      QueryGroup currentGroup = (QueryGroup)queryStack.peek();
537
      currentGroup.addChild(currentTerm);
538
      currentValue = null;
539
      currentPathexpr = null;
540
    } else if (leaving.getTagName().equals("querygroup")) {
541
      QueryGroup leavingGroup = (QueryGroup)queryStack.pop();
542
    }
543
  }
544

    
545
  /**
546
   * callback method used by the SAX Parser when the text sequences of an 
547
   * xml stream are detected. Used in this context to parse and store
548
   * the query information in class variables.
549
   */
550
  public void characters(char ch[], int start, int length) {
551

    
552
    String inputString = new String(ch, start, length);
553
    BasicNode currentNode = (BasicNode)elementStack.peek(); 
554
    String currentTag = currentNode.getTagName();
555
    if (currentTag.equals("meta_file_id")) {
556
      meta_file_id = inputString;
557
    } else if (currentTag.equals("querytitle")) {
558
      queryTitle = inputString;
559
    } else if (currentTag.equals("value")) {
560
      currentValue = inputString;
561
    } else if (currentTag.equals("pathexpr")) {
562
      currentPathexpr = inputString;
563
    } else if (currentTag.equals("returndoctype")) {
564
      returnDocList.add(inputString);
565
    } else if (currentTag.equals("filterdoctype")) {
566
      filterDocList.add(inputString);
567
    } else if (currentTag.equals("returnfield")) {
568
      returnFieldList.add(inputString);
569
      containsExtendedSQL = true;
570
    } else if (currentTag.equals("filterdoctype")) {
571
      filterDocList.add(inputString);
572
    } else if (currentTag.equals("owner")) {
573
      ownerList.add(inputString);
574
    } else if (currentTag.equals("site")) {
575
      siteList.add(inputString);
576
    }
577
  }
578

    
579
  /**
580
   * create a SQL serialization of the query that this instance represents
581
   */
582
  public String printSQL(boolean useXMLIndex) {
583
    StringBuffer self = new StringBuffer();
584

    
585
    self.append("SELECT docid,docname,doctype,");
586
    self.append("date_created, date_updated, rev ");
587
    self.append("FROM xml_documents WHERE docid IN (");
588

    
589
    // This determines the documents that meet the query conditions
590
    self.append(query.printSQL(useXMLIndex));
591

    
592
    self.append(") ");
593
 
594
    // Add SQL to filter for doctypes requested in the query
595
    // This is an implicit OR for the list of doctypes. Only doctypes in this
596
    // list will be searched if the tag is present
597
    if (!filterDocList.isEmpty()) {
598
      boolean firstdoctype = true;
599
      self.append(" AND ("); 
600
      Enumeration en = filterDocList.elements();
601
      while (en.hasMoreElements()) {
602
        String currentDoctype = (String)en.nextElement();
603
        if (firstdoctype) {
604
           firstdoctype = false;
605
           self.append(" doctype = '" + currentDoctype + "'"); 
606
        } else {
607
          self.append(" OR doctype = '" + currentDoctype + "'"); 
608
        }
609
      }
610
      self.append(") ");
611
    }
612

    
613
    // Add SQL to filter for owners requested in the query
614
    // This is an implicit OR for the list of owners
615
    if (!ownerList.isEmpty()) {
616
      boolean first = true;
617
      self.append(" AND ("); 
618
      Enumeration en = ownerList.elements();
619
      while (en.hasMoreElements()) {
620
        String current = (String)en.nextElement();
621
        if (first) {
622
           first = false;
623
           self.append(" user_owner = '" + current + "'"); 
624
        } else {
625
          self.append(" OR user_owner = '" + current + "'"); 
626
        }
627
      }
628
      self.append(") ");
629
    }
630

    
631
    // Add SQL to filter for sites requested in the query
632
    // This is an implicit OR for the list of sites
633
    if (!siteList.isEmpty()) {
634
      boolean first = true;
635
      self.append(" AND ("); 
636
      Enumeration en = siteList.elements();
637
      while (en.hasMoreElements()) {
638
        String current = (String)en.nextElement();
639
        if (first) {
640
           first = false;
641
           self.append(" SUBSTR(docid, 1, INSTR(docid, '" +
642
               accNumberSeparator + "')-1) = '" + current + "'"); 
643
        } else {
644
          self.append(" OR SUBSTR(docid, 1, INSTR(docid, '" +
645
               accNumberSeparator + "')-1) = '" + current + "'"); 
646
        }
647
      }
648
      self.append(") ");
649
    }
650
    //System.out.println(self.toString());
651
    return self.toString();
652
  }
653
  
654
  /**
655
   * This method prints sql based upon the &lt;returnfield&gt; tag in the
656
   * pathquery document.  This allows for customization of the 
657
   * returned fields
658
   * @param doclist the list of document ids to search by
659
   */
660
  public String printExtendedSQL(String doclist)
661
  {  
662
    StringBuffer self = new StringBuffer();
663
    self.append("select xml_nodes.docid, xml_index.path, xml_nodes.nodedata ");
664
    self.append("from xml_index, xml_nodes where xml_index.nodeid=");
665
    self.append("xml_nodes.parentnodeid and (xml_index.path like '");
666
    boolean firstfield = true;
667
    //put the returnfields into the query
668
    //the for loop allows for multiple fields
669
    for(int i=0; i<returnFieldList.size(); i++)
670
    {
671
      if(firstfield)
672
      {
673
        firstfield = false;
674
        self.append((String)returnFieldList.elementAt(i));
675
        self.append("' ");
676
      }
677
      else
678
      {
679
        self.append("or xml_index.path like '");
680
        self.append((String)returnFieldList.elementAt(i));
681
        self.append("' ");
682
      }
683
    }
684
    self.append(") AND xml_nodes.docid in (");
685
    //self.append(query.printSQL());
686
    self.append(doclist);
687
    self.append(")");
688
    self.append(" AND xml_nodes.nodetype = 'TEXT'");
689

    
690
    //System.out.println(self.toString());
691
    return self.toString();
692
  }
693
  
694
  public static String printRelationSQL(String docid)
695
  {
696
    StringBuffer self = new StringBuffer();
697
    self.append("select subject, relationship, object, subdoctype, ");
698
    self.append("objdoctype from xml_relation ");
699
    self.append("where docid like '").append(docid).append("'");
700
    return self.toString();
701
  }
702
   
703
  /**
704
   * Prints sql that returns all relations in the database.
705
   */
706
  public static String printPackageSQL()
707
  {
708
    StringBuffer self = new StringBuffer();
709
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
710
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
711
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
712
    self.append("from xml_index where path like ");
713
    self.append("'triple/relationship') rel, ");
714
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
715
    self.append("'triple/object') o, ");
716
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
717
    self.append("where s.parentnodeid = rel.parentnodeid ");
718
    self.append("and rel.parentnodeid = o.parentnodeid ");
719
    self.append("and x.parentnodeid in (rel.nodeid) ");
720
    self.append("and y.parentnodeid in (o.nodeid) ");
721
    self.append("and z.parentnodeid in (s.nodeid) ");
722
    //self.append("and z.nodedata like '%");
723
    //self.append(docid);
724
    //self.append("%'");
725
    return self.toString();
726
  }
727
  
728
  /**
729
   * Prints sql that returns all relations in the database that were input
730
   * under a specific docid
731
   * @param docid the docid to search for.
732
   */
733
  public static String printPackageSQL(String docid)
734
  {
735
    StringBuffer self = new StringBuffer();
736
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
737
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
738
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
739
    self.append("from xml_index where path like ");
740
    self.append("'triple/relationship') rel, ");
741
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
742
    self.append("'triple/object') o, ");
743
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
744
    self.append("where s.parentnodeid = rel.parentnodeid ");
745
    self.append("and rel.parentnodeid = o.parentnodeid ");
746
    self.append("and x.parentnodeid in (rel.nodeid) ");
747
    self.append("and y.parentnodeid in (o.nodeid) ");
748
    self.append("and z.parentnodeid in (s.nodeid) ");
749
    self.append("and z.docid like '").append(docid).append("'");
750
    
751
    return self.toString();
752
  }
753
  
754
  /**
755
   * Returns all of the relations that has a certain docid in the subject
756
   * or the object.
757
   * 
758
   * @param docid the docid to search for
759
   */
760
  public static String printPackageSQL(String subDocidURL, String objDocidURL)
761
  {
762
    StringBuffer self = new StringBuffer();
763
    self.append("select z.nodedata, x.nodedata, y.nodedata from ");
764
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
765
    self.append("'triple/subject') s, (select nodeid, parentnodeid ");
766
    self.append("from xml_index where path like ");
767
    self.append("'triple/relationship') rel, ");
768
    self.append("(select nodeid, parentnodeid from xml_index where path like ");
769
    self.append("'triple/object') o, ");
770
    self.append("xml_nodes x, xml_nodes y, xml_nodes z ");
771
    self.append("where s.parentnodeid = rel.parentnodeid ");
772
    self.append("and rel.parentnodeid = o.parentnodeid ");
773
    self.append("and x.parentnodeid in (rel.nodeid) ");
774
    self.append("and y.parentnodeid in (o.nodeid) ");
775
    self.append("and z.parentnodeid in (s.nodeid) ");
776
    self.append("and (z.nodedata like '");
777
    self.append(subDocidURL);
778
    self.append("' or y.nodedata like '");
779
    self.append(objDocidURL);
780
    self.append("')");
781
    return self.toString();
782
  }
783
  
784
  public static String printGetDocByDoctypeSQL(String docid)
785
  {
786
    StringBuffer self = new StringBuffer();
787

    
788
    self.append("SELECT docid,docname,doctype,");
789
    self.append("date_created, date_updated ");
790
    self.append("FROM xml_documents WHERE docid IN (");
791
    self.append(docid).append(")");
792
    return self.toString();
793
  }
794
  
795
  /**
796
   * create a String description of the query that this instance represents.
797
   * This should become a way to get the XML serialization of the query.
798
   */
799
  public String toString() {
800
    return "meta_file_id=" + meta_file_id + "\n" + query;
801
//DOCTITLE attr cleared from the db
802
//    return "meta_file_id=" + meta_file_id + "\n" + 
803
//           "querytitle=" + querytitle + "\n" + query;
804
  }
805

    
806
  /** a utility class that represents a group of terms in a query */
807
  private class QueryGroup {
808
    private String operator = null;  // indicates how query terms are combined
809
    private Vector children = null;  // the list of query terms and groups
810

    
811
    /** 
812
     * construct a new QueryGroup 
813
     *
814
     * @param operator the boolean conector used to connect query terms 
815
     *                    in this query group
816
     */
817
    public QueryGroup(String operator) {
818
      this.operator = operator;
819
      children = new Vector();
820
    }
821

    
822
    /** 
823
     * Add a child QueryGroup to this QueryGroup
824
     *
825
     * @param qgroup the query group to be added to the list of terms
826
     */
827
    public void addChild(QueryGroup qgroup) {
828
      children.add((Object)qgroup); 
829
    }
830

    
831
    /**
832
     * Add a child QueryTerm to this QueryGroup
833
     *
834
     * @param qterm the query term to be added to the list of terms
835
     */
836
    public void addChild(QueryTerm qterm) {
837
      children.add((Object)qterm); 
838
    }
839

    
840
    /**
841
     * Retrieve an Enumeration of query terms for this QueryGroup
842
     */
843
    public Enumeration getChildren() {
844
      return children.elements();
845
    }
846
   
847
    /**
848
     * create a SQL serialization of the query that this instance represents
849
     */
850
    public String printSQL(boolean useXMLIndex) {
851
      StringBuffer self = new StringBuffer();
852
      boolean first = true;
853

    
854
      self.append("(");
855

    
856
      Enumeration en= getChildren();
857
      while (en.hasMoreElements()) {
858
        Object qobject = en.nextElement();
859
        if (first) {
860
          first = false;
861
        } else {
862
          self.append(" " + operator + " ");
863
        }
864
        if (qobject instanceof QueryGroup) {
865
          QueryGroup qg = (QueryGroup)qobject;
866
          self.append(qg.printSQL(useXMLIndex));
867
        } else if (qobject instanceof QueryTerm) {
868
          QueryTerm qt = (QueryTerm)qobject;
869
          self.append(qt.printSQL(useXMLIndex));
870
        } else {
871
          System.err.println("qobject wrong type: fatal error");
872
        }
873
      }
874
      self.append(") \n");
875
      return self.toString();
876
    }
877

    
878
    /**
879
     * create a String description of the query that this instance represents.
880
     * This should become a way to get the XML serialization of the query.
881
     */
882
    public String toString() {
883
      StringBuffer self = new StringBuffer();
884

    
885
      self.append("  (Query group operator=" + operator + "\n");
886
      Enumeration en= getChildren();
887
      while (en.hasMoreElements()) {
888
        Object qobject = en.nextElement();
889
        self.append(qobject);
890
      }
891
      self.append("  )\n");
892
      return self.toString();
893
    }
894
  }
895

    
896
  /** a utility class that represents a single term in a query */
897
  private class QueryTerm {
898
    private boolean casesensitive = false;
899
    private String searchmode = null;
900
    private String value = null;
901
    private String pathexpr = null;
902

    
903
    /**
904
     * Construct a new instance of a query term for a free text search
905
     * (using the value only)
906
     *
907
     * @param casesensitive flag indicating whether case is used to match
908
     * @param searchmode determines what kind of substring match is performed
909
     *        (one of starts-with|ends-with|contains|matches-exactly)
910
     * @param value the text value to match
911
     */
912
    public QueryTerm(boolean casesensitive, String searchmode, 
913
                     String value) {
914
      this.casesensitive = casesensitive;
915
      this.searchmode = searchmode;
916
      this.value = value;
917
    }
918

    
919
    /**
920
     * Construct a new instance of a query term for a structured search
921
     * (matching the value only for those nodes in the pathexpr)
922
     *
923
     * @param casesensitive flag indicating whether case is used to match
924
     * @param searchmode determines what kind of substring match is performed
925
     *        (one of starts-with|ends-with|contains|matches-exactly)
926
     * @param value the text value to match
927
     * @param pathexpr the hierarchical path to the nodes to be searched
928
     */
929
    public QueryTerm(boolean casesensitive, String searchmode, 
930
                     String value, String pathexpr) {
931
      this(casesensitive, searchmode, value);
932
      this.pathexpr = pathexpr;
933
    }
934

    
935
    /** determine if the QueryTerm is case sensitive */
936
    public boolean isCaseSensitive() {
937
      return casesensitive;
938
    }
939

    
940
    /** get the searchmode parameter */
941
    public String getSearchMode() {
942
      return searchmode;
943
    }
944
 
945
    /** get the Value parameter */
946
    public String getValue() {
947
      return value;
948
    }
949

    
950
    /** get the path expression parameter */
951
    public String getPathExpression() {
952
      return pathexpr;
953
    }
954

    
955
    /**
956
     * create a SQL serialization of the query that this instance represents
957
     */
958
    public String printSQL(boolean useXMLIndex) {
959
      StringBuffer self = new StringBuffer();
960

    
961
      // Uppercase the search string if case match is not important
962
      String casevalue = null;
963
      String nodedataterm = null;
964

    
965
      if (casesensitive) {
966
        nodedataterm = "nodedata";
967
        casevalue = value;
968
      } else {
969
        nodedataterm = "UPPER(nodedata)";
970
        casevalue = value.toUpperCase();
971
      }
972

    
973
      // Add appropriate wildcards to search string
974
      //String searchvalue = null;
975
      String searchexpr = null;
976
      if (searchmode.equals("starts-with")) {
977
        //searchvalue = casevalue + "%";
978
        searchexpr = nodedataterm + " LIKE '" + casevalue + "%' ";
979
      } else if (searchmode.equals("ends-with")) {
980
        //searchvalue = "%" + casevalue;
981
        searchexpr = nodedataterm + " LIKE '%" + casevalue + "' ";
982
      } else if (searchmode.equals("contains")) {
983
        //searchvalue = "%" + casevalue + "%";
984
        if (!casevalue.equals("%"))
985
        {
986
          searchexpr = nodedataterm + " LIKE '%" + casevalue + "%' ";
987
        }
988
        else
989
        {
990
          searchexpr = nodedataterm + " LIKE '" + casevalue + "' ";
991
        }
992
      } else if (searchmode.equals("equals")) {
993
        //searchvalue = casevalue;
994
        searchexpr = nodedataterm + " = '" + casevalue + "' ";
995
      } else if (searchmode.equals("isnot-equal")) {
996
        //searchvalue = casevalue;
997
        searchexpr = nodedataterm + " != '" + casevalue + "' ";
998
      } else { 
999
        //searchvalue = casevalue;
1000
        String oper = null;
1001
        if (searchmode.equals("greater-than")) {
1002
          oper = ">";
1003
        } else if (searchmode.equals("greater-than-equals")) {
1004
          oper = ">=";
1005
        } else if (searchmode.equals("less-than")) {
1006
          oper = "<";
1007
        } else if (searchmode.equals("less-than-equals")) {
1008
          oper = "<=";
1009
        } else {
1010
          System.out.println("NOT expected case. NOT recognized operator: " +
1011
                             searchmode);
1012
          return null;
1013
        }
1014
        try {
1015
          // it is number; numeric comparison
1016
          // but we need to make sure there is no string in node data
1017
          String getRidOfString = " AND UPPER(nodedata) = LOWER(nodedata)" +
1018
                                  " AND LTRIM(nodedata) != ' ' " +
1019
                                  " AND nodedata IS NOT NULL ";
1020
          searchexpr = nodedataterm + " " + oper + " " +
1021
                       new Double(casevalue) + " "+getRidOfString;          
1022
        } catch (NumberFormatException nfe) {
1023
          // these are characters; character comparison
1024
          searchexpr = nodedataterm + " " + oper + " '" + casevalue + "' ";
1025
        }
1026
      }
1027

    
1028
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
1029
      //self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
1030
      self.append(searchexpr);
1031

    
1032
      if (pathexpr != null) {
1033
        self.append("AND parentnodeid IN ");
1034
        // use XML Index
1035
        if ( useXMLIndex ) {
1036
          self.append("(SELECT nodeid FROM xml_index WHERE path LIKE " + 
1037
                      "'" +  pathexpr + "') " );
1038
        // without using XML Index; using nested statements instead
1039
        } else {
1040
          self.append(useNestedStatements(pathexpr));
1041
        }
1042
      }
1043

    
1044
      return self.toString();
1045
    }
1046

    
1047
    /* 
1048
     * Constraint the query with @pathexp without using the XML Index,
1049
     * but nested SQL statements instead. The query migth be slower.
1050
     */
1051
    private String useNestedStatements(String pathexpr)
1052
    {
1053
      StringBuffer nestedStmts = new StringBuffer();
1054
      Vector nodes = new Vector();
1055
      String path = pathexpr;
1056
      int inx = 0;
1057

    
1058
      do {
1059
        inx = path.lastIndexOf("/");
1060
//System.out.println(path.substring(inx+1));
1061
        nodes.addElement(path.substring(inx+1));
1062
        path = path.substring(0, Math.abs(inx));
1063
      } while ( inx > 0 );
1064
      
1065
      // nested statements
1066
      int i = 0;
1067
      for (i = 0; i < nodes.size()-1; i++) {
1068
        nestedStmts.append("(SELECT nodeid FROM xml_nodes" + 
1069
                           " WHERE nodename LIKE '" +
1070
                             (String)nodes.elementAt(i) + "'" +
1071
                           " AND parentnodeid IN ");
1072
      }
1073
      // for the last statement: it is without " AND parentnodeid IN "
1074
      nestedStmts.append("(SELECT nodeid FROM xml_nodes" + 
1075
                         " WHERE nodename LIKE '" +
1076
                         (String)nodes.elementAt(i) + "'" );
1077
      // node.size() number of closing brackets
1078
      for (i = 0; i < nodes.size(); i++) {
1079
        nestedStmts.append(")");
1080
      }
1081

    
1082

    
1083
//System.out.println(nestedStmts.toString());
1084
      return nestedStmts.toString();
1085
    }
1086

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

    
1093
      return this.printSQL(true);
1094
    }
1095
  }
1096
}
(41-41/48)