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-21 13:08:59 -0700 (Tue, 21 Oct 2003) $'
14
 * '$Revision: 1839 $'
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
          nodedataterm = "nodedata";
163
        } else if (searchmode.equals("greater-than-equals")) {
164
          oper = ">=";
165
          nodedataterm = "nodedata";
166
        } else if (searchmode.equals("less-than")) {
167
          oper = "<";
168
          nodedataterm = "nodedata";
169
        } else if (searchmode.equals("less-than-equals")) {
170
          oper = "<=";
171
          nodedataterm = "nodedata";
172
        } else {
173
          System.out.println("NOT expected case. NOT recognized operator: " +
174
                             searchmode);
175
          return null;
176
        }
177
        try {
178
          // it is number; numeric comparison
179
          // but we need to make sure there is no string in node data
180
          String getRidOfString = " AND UPPER(nodedata) = LOWER(nodedata)" +
181
                                  " AND LTRIM(nodedata) != ' ' " +
182
                                  " AND nodedata IS NOT NULL ";
183
          searchexpr = nodedataterm + " " + oper + " " +
184
                       new Double(casevalue) + " "+getRidOfString;          
185
        } catch (NumberFormatException nfe) {
186
          // these are characters; character comparison
187
          searchexpr = nodedataterm + " " + oper + " '" + casevalue + "' ";
188
        }
189
      }
190

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

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

    
263
      do {
264
        inx = path.lastIndexOf("/");
265

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

    
287

    
288

    
289
      return nestedStmts.toString();
290
    }
291

    
292
    /**
293
     * create a String description of the query that this instance represents.
294
     * This should become a way to get the XML serialization of the query.
295
     */
296
    public String toString() {
297

    
298
      return this.printSQL(true);
299
    }
300
  }
(49-49/58)