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: jones $'
13
 *     '$Date: 2004-03-29 11:24:05 -0800 (Mon, 29 Mar 2004) $'
14
 * '$Revision: 2069 $'
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 java.util.Vector;
34

    
35
/** a utility class that represents a single term in a query */
36
public class QueryTerm
37
{
38

    
39
    private boolean casesensitive = false;
40

    
41
    private String searchmode = null;
42

    
43
    private String value = null;
44

    
45
    private String pathexpr = null;
46

    
47
    private boolean percentageSymbol = false;
48

    
49
    private int countPercentageSearchItem = 0;
50

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

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

    
91
    /** determine if the QueryTerm is case sensitive */
92
    public boolean isCaseSensitive()
93
    {
94
        return casesensitive;
95
    }
96

    
97
    /** get the searchmode parameter */
98
    public String getSearchMode()
99
    {
100
        return searchmode;
101
    }
102

    
103
    /** get the Value parameter */
104
    public String getValue()
105
    {
106
        return value;
107
    }
108

    
109
    /** get the path expression parameter */
110
    public String getPathExpression()
111
    {
112
        return pathexpr;
113
    }
114

    
115
    /** get the percentage count for one query term */
116
    public int getPercentageSymbolCount()
117
    {
118
        return countPercentageSearchItem;
119
    }
120

    
121
    /**
122
     * create a SQL serialization of the query that this instance represents
123
     */
124
    public String printSQL(boolean useXMLIndex)
125
    {
126
        StringBuffer self = new StringBuffer();
127

    
128
        // Uppercase the search string if case match is not important
129
        String casevalue = null;
130
        String nodedataterm = null;
131

    
132
        if (casesensitive) {
133
            nodedataterm = "nodedata";
134
            casevalue = value;
135
        } else {
136
            nodedataterm = "UPPER(nodedata)";
137
            casevalue = value.toUpperCase();
138
        }
139

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

    
195
        self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n");
196
        self.append(searchexpr);
197
        if (pathexpr != null) {
198

    
199
            // use XML Index
200
            if (useXMLIndex) {
201
                if (!hasAttributeInPath(pathexpr)) {
202
                    // without attributes in path
203
                    self.append("AND parentnodeid IN ");
204
                } else {
205
                    // has a attribute in path
206
                    String attributeName = QuerySpecification
207
                            .getAttributeName(pathexpr);
208
                    self.append("AND nodetype LIKE 'ATTRIBUTE' AND nodename LIKE '"
209
                                    + attributeName + "' ");
210
                    self.append("AND parentnodeid IN ");
211
                    pathexpr = QuerySpecification
212
                            .newPathExpressionWithOutAttribute(pathexpr);
213

    
214
                }
215
                self.append("(SELECT nodeid FROM xml_index WHERE path LIKE "
216
                        + "'" + pathexpr + "') ");
217
            } else {
218
                // without using XML Index; using nested statements instead
219
                self.append("AND parentnodeid IN ");
220
                self.append(useNestedStatements(pathexpr));
221
            }
222
        } else if ((value.trim()).equals("%")) {
223
            //if pathexpr is null and search value is %, is a
224
            // 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
            return true;
238
        } else {
239
            return false;
240
        }
241
    }
242

    
243
    /*
244
     * Constraint the query with @pathexp without using the XML Index, but
245
     * nested SQL statements instead. The query migth be slower.
246
     */
247
    public static String useNestedStatements(String pathexpr)
248
    {
249
        StringBuffer nestedStmts = new StringBuffer();
250
        Vector nodes = new Vector();
251
        String path = pathexpr;
252
        int inx = 0;
253

    
254
        do {
255
            inx = path.lastIndexOf("/");
256

    
257
            nodes.addElement(path.substring(inx + 1));
258
            path = path.substring(0, Math.abs(inx));
259
        } while (inx > 0);
260

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

    
276
        return nestedStmts.toString();
277
    }
278

    
279
    /**
280
     * create a String description of the query that this instance represents.
281
     * This should become a way to get the XML serialization of the query.
282
     */
283
    public String toString()
284
    {
285

    
286
        return this.printSQL(true);
287
    }
288
}
(51-51/61)