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-10-02 17:16:37 -0700 (Thu, 02 Oct 2003) $'
14
 * '$Revision: 1831 $'
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
/** a utility class that represents a single term in a query */
42
public class QueryTerm {
43
    private boolean casesensitive = false;
44
    private String searchmode = null;
45
    private String value = null;
46
    private String pathexpr = null;
47
    private boolean percentageSymbol = false;
48
    private int countPercentageSearchItem = 0;
49
   
50

    
51
    /**
52
     * Construct a new instance of a query term for a free text search
53
     * (using the value only)
54
     *
55
     * @param casesensitive flag indicating whether case is used to match
56
     * @param searchmode determines what kind of substring match is performed
57
     *        (one of starts-with|ends-with|contains|matches-exactly)
58
     * @param value the text value to match
59
     */
60
    public QueryTerm(boolean casesensitive, String searchmode, 
61
                     String value) {
62
      this.casesensitive = casesensitive;
63
      this.searchmode = searchmode;
64
      this.value = value;
65
    }
66

    
67
    /**
68
     * Construct a new instance of a query term for a structured search
69
     * (matching the value only for those nodes in the pathexpr)
70
     *
71
     * @param casesensitive flag indicating whether case is used to match
72
     * @param searchmode determines what kind of substring match is performed
73
     *        (one of starts-with|ends-with|contains|matches-exactly)
74
     * @param value the text value to match
75
     * @param pathexpr the hierarchical path to the nodes to be searched
76
     */
77
    public QueryTerm(boolean casesensitive, String searchmode, 
78
                     String value, String pathexpr) {
79
      this(casesensitive, searchmode, value);
80
      this.pathexpr = pathexpr;
81
    }
82

    
83
    /** determine if the QueryTerm is case sensitive */
84
    public boolean isCaseSensitive() {
85
      return casesensitive;
86
    }
87

    
88
    /** get the searchmode parameter */
89
    public String getSearchMode() {
90
      return searchmode;
91
    }
92
 
93
    /** get the Value parameter */
94
    public String getValue() {
95
      return value;
96
    }
97

    
98
    /** get the path expression parameter */
99
    public String getPathExpression() {
100
      return pathexpr;
101
    }
102
    
103
    /** get the percentage count for one query term*/
104
    public int getPercentageSymbolCount()
105
    {
106
      return countPercentageSearchItem;
107
    }
108

    
109
    /**
110
     * create a SQL serialization of the query that this instance represents
111
     */
112
    public String printSQL(boolean useXMLIndex) {
113
      StringBuffer self = new StringBuffer();
114

    
115
      // Uppercase the search string if case match is not important
116
      String casevalue = null;
117
      String nodedataterm = null;
118

    
119
      if (casesensitive) {
120
        nodedataterm = "nodedata";
121
        casevalue = value;
122
      } else {
123
        nodedataterm = "UPPER(nodedata)";
124
        casevalue = value.toUpperCase();
125
      }
126

    
127
      // Add appropriate wildcards to search string
128
      //String searchvalue = null;
129
      String searchexpr = null;
130
      if (searchmode.equals("starts-with")) {
131
        //searchvalue = casevalue + "%";
132
        searchexpr = nodedataterm + " LIKE '" + casevalue + "%' ";
133
      } else if (searchmode.equals("ends-with")) {
134
        //searchvalue = "%" + casevalue;
135
        searchexpr = nodedataterm + " LIKE '%" + casevalue + "' ";
136
      } else if (searchmode.equals("contains")) {
137
        //searchvalue = "%" + casevalue + "%";
138
        if (!casevalue.equals("%"))
139
        {
140
          searchexpr = nodedataterm + " LIKE '%" + casevalue + "%' ";
141
        }
142
        else
143
        {
144
          searchexpr = nodedataterm + " LIKE '" + casevalue + "' ";
145
          // find percentage symbol
146
          percentageSymbol = true;
147
        }
148
      } else if (searchmode.equals("equals")) {
149
        //searchvalue = casevalue;
150
        searchexpr = nodedataterm + " = '" + casevalue + "' ";
151
      } else if (searchmode.equals("isnot-equal")) {
152
        //searchvalue = casevalue;
153
        searchexpr = nodedataterm + " != '" + casevalue + "' ";
154
      } else { 
155
        //searchvalue = casevalue;
156
        String oper = null;
157
        if (searchmode.equals("greater-than")) {
158
          oper = ">";
159
        } else if (searchmode.equals("greater-than-equals")) {
160
          oper = ">=";
161
        } else if (searchmode.equals("less-than")) {
162
          oper = "<";
163
        } else if (searchmode.equals("less-than-equals")) {
164
          oper = "<=";
165
        } else {
166
          System.out.println("NOT expected case. NOT recognized operator: " +
167
                             searchmode);
168
          return null;
169
        }
170
        try {
171
          // it is number; numeric comparison
172
          // but we need to make sure there is no string in node data
173
          String getRidOfString = " AND UPPER(nodedata) = LOWER(nodedata)" +
174
                                  " AND LTRIM(nodedata) != ' ' " +
175
                                  " AND nodedata IS NOT NULL ";
176
          searchexpr = nodedataterm + " " + oper + " " +
177
                       new Double(casevalue) + " "+getRidOfString;          
178
        } catch (NumberFormatException nfe) {
179
          // these are characters; character comparison
180
          searchexpr = nodedataterm + " " + oper + " '" + casevalue + "' ";
181
        }
182
      }
183

    
184
      self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
185
      //self.append(nodedataterm + " LIKE " + "'" + searchvalue + "' ");
186
      self.append(searchexpr);
187
      if (pathexpr != null) 
188
      {
189
        
190
        // use XML Index
191
        if ( useXMLIndex ) 
192
        {
193
          if (!hasAttributeInPath(pathexpr))
194
          {
195
            // without attributes in path
196
            self.append("AND parentnodeid IN ");
197
          }
198
          else
199
          {
200
            // has a attribute in path
201
            String attributeName = QuerySpecification.getAttributeName(pathexpr);
202
            self.append("AND nodetype LIKE 'ATTRIBUTE' AND nodename LIKE '"+
203
                        attributeName + "' ");
204
            self.append("AND parentnodeid IN ");
205
            pathexpr = 
206
                 QuerySpecification.newPathExpressionWithOutAttribute(pathexpr);
207
            
208
          } 
209
          self.append("(SELECT nodeid FROM xml_index WHERE path LIKE " + 
210
                      "'" +  pathexpr + "') " );
211
        } 
212
        else 
213
        {
214
          // without using XML Index; using nested statements instead
215
          self.append("AND parentnodeid IN ");
216
          self.append(useNestedStatements(pathexpr));
217
        }
218
      }
219
      else if ((value.trim()).equals("%"))
220
      {
221
        //if pathexpr is null and search value is %, is a percentageSearchItem
222
        // the count number will be increase one
223
        countPercentageSearchItem++;
224
        
225
       }
226

    
227
      return self.toString();
228
    }
229
    
230
    /* A method to judge if a path have attribute */
231
    private boolean hasAttributeInPath(String path)
232
    {
233
      if (path.indexOf(QuerySpecification.ATTRIBUTESYMBOL)!=-1)
234
      {
235
        return true;
236
      }
237
      else
238
      {
239
        return false;
240
      }
241
    }
242
    
243
   
244
    
245
    /* 
246
     * Constraint the query with @pathexp without using the XML Index,
247
     * but nested SQL statements instead. The query migth be slower.
248
     */
249
    private String useNestedStatements(String pathexpr)
250
    {
251
      StringBuffer nestedStmts = new StringBuffer();
252
      Vector nodes = new Vector();
253
      String path = pathexpr;
254
      int inx = 0;
255

    
256
      do {
257
        inx = path.lastIndexOf("/");
258

    
259
        nodes.addElement(path.substring(inx+1));
260
        path = path.substring(0, Math.abs(inx));
261
      } while ( inx > 0 );
262
      
263
      // nested statements
264
      int i = 0;
265
      for (i = 0; i < nodes.size()-1; i++) {
266
        nestedStmts.append("(SELECT nodeid FROM xml_nodes" + 
267
                           " WHERE nodename LIKE '" +
268
                             (String)nodes.elementAt(i) + "'" +
269
                           " AND parentnodeid IN ");
270
      }
271
      // for the last statement: it is without " AND parentnodeid IN "
272
      nestedStmts.append("(SELECT nodeid FROM xml_nodes" + 
273
                         " WHERE nodename LIKE '" +
274
                         (String)nodes.elementAt(i) + "'" );
275
      // node.size() number of closing brackets
276
      for (i = 0; i < nodes.size(); i++) {
277
        nestedStmts.append(")");
278
      }
279

    
280

    
281

    
282
      return nestedStmts.toString();
283
    }
284

    
285
    /**
286
     * create a String description of the query that this instance represents.
287
     * This should become a way to get the XML serialization of the query.
288
     */
289
    public String toString() {
290

    
291
      return this.printSQL(true);
292
    }
293
  }
(49-49/58)