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-20 16:05:22 -0700 (Mon, 20 Oct 2003) $'
14
 * '$Revision: 1838 $'
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("not-contains")) {
149
        //searchvalue = "%" + casevalue;
150
        searchexpr = nodedataterm + " NOT LIKE '%" + casevalue + "%' ";
151
      } else if (searchmode.equals("equals")) {
152
        //searchvalue = casevalue;
153
        searchexpr = nodedataterm + " = '" + casevalue + "' ";
154
      } else if (searchmode.equals("isnot-equal")) {
155
        //searchvalue = casevalue;
156
        searchexpr = nodedataterm + " != '" + casevalue + "' ";
157
      } else { 
158
        //searchvalue = casevalue;
159
        String oper = null;
160
        if (searchmode.equals("greater-than")) {
161
          oper = ">";
162
        } else if (searchmode.equals("greater-than-equals")) {
163
          oper = ">=";
164
        } else if (searchmode.equals("less-than")) {
165
          oper = "<";
166
        } else if (searchmode.equals("less-than-equals")) {
167
          oper = "<=";
168
        } else {
169
          System.out.println("NOT expected case. NOT recognized operator: " +
170
                             searchmode);
171
          return null;
172
        }
173
        try {
174
          // it is number; numeric comparison
175
          // but we need to make sure there is no string in node data
176
          String getRidOfString = " AND UPPER(nodedata) = LOWER(nodedata)" +
177
                                  " AND LTRIM(nodedata) != ' ' " +
178
                                  " AND nodedata IS NOT NULL ";
179
          searchexpr = nodedataterm + " " + oper + " " +
180
                       new Double(casevalue) + " "+getRidOfString;          
181
        } catch (NumberFormatException nfe) {
182
          // these are characters; character comparison
183
          searchexpr = nodedataterm + " " + oper + " '" + casevalue + "' ";
184
        }
185
      }
186

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

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

    
259
      do {
260
        inx = path.lastIndexOf("/");
261

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

    
283

    
284

    
285
      return nestedStmts.toString();
286
    }
287

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

    
294
      return this.printSQL(true);
295
    }
296
  }
(49-49/58)