Project

General

Profile

1 1831 tao
/**
2
 *  '$RCSfile$'
3 2357 sgarg
 *    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 1831 tao
 *             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
 *
11
 *   '$Author$'
12
 *     '$Date$'
13
 * '$Revision$'
14
 *
15
 * This program is free software; you can redistribute it and/or modify
16
 * it under the terms of the GNU General Public License as published by
17
 * the Free Software Foundation; either version 2 of the License, or
18
 * (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 * GNU General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU General Public License
26
 * along with this program; if not, write to the Free Software
27
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
28
 */
29
30
package edu.ucsb.nceas.metacat;
31
32
import java.util.Vector;
33 3235 sledge
import org.apache.log4j.Logger;
34 1831 tao
35
/** a utility class that represents a single term in a query */
36 2068 jones
public class QueryTerm
37
{
38 3235 sledge
    private static Logger log = Logger.getLogger(QueryTerm.class);
39 2068 jones
40 1831 tao
    private boolean casesensitive = false;
41 2068 jones
42 1831 tao
    private String searchmode = null;
43 2068 jones
44 1831 tao
    private String value = null;
45 2068 jones
46 1831 tao
    private String pathexpr = null;
47 2068 jones
48 1831 tao
    private boolean percentageSymbol = false;
49 2068 jones
50 1831 tao
    private int countPercentageSearchItem = 0;
51
52
    /**
53 2068 jones
     * Construct a new instance of a query term for a free text search (using
54
     * the value only)
55 2357 sgarg
     *
56 2068 jones
     * @param casesensitive
57
     *            flag indicating whether case is used to match
58
     * @param searchmode
59
     *            determines what kind of substring match is performed (one of
60
     *            starts-with|ends-with|contains|matches-exactly)
61
     * @param value
62
     *            the text value to match
63 1831 tao
     */
64 2068 jones
    public QueryTerm(boolean casesensitive, String searchmode, String value)
65
    {
66
        this.casesensitive = casesensitive;
67
        this.searchmode = searchmode;
68
        this.value = value;
69 1831 tao
    }
70
71
    /**
72
     * Construct a new instance of a query term for a structured search
73
     * (matching the value only for those nodes in the pathexpr)
74 2357 sgarg
     *
75 2068 jones
     * @param casesensitive
76
     *            flag indicating whether case is used to match
77
     * @param searchmode
78
     *            determines what kind of substring match is performed (one of
79
     *            starts-with|ends-with|contains|matches-exactly)
80
     * @param value
81
     *            the text value to match
82
     * @param pathexpr
83
     *            the hierarchical path to the nodes to be searched
84 1831 tao
     */
85 2068 jones
    public QueryTerm(boolean casesensitive, String searchmode, String value,
86
            String pathexpr)
87
    {
88
        this(casesensitive, searchmode, value);
89
        this.pathexpr = pathexpr;
90 1831 tao
    }
91
92
    /** determine if the QueryTerm is case sensitive */
93 2068 jones
    public boolean isCaseSensitive()
94
    {
95
        return casesensitive;
96 1831 tao
    }
97
98
    /** get the searchmode parameter */
99 2068 jones
    public String getSearchMode()
100
    {
101
        return searchmode;
102 1831 tao
    }
103 2068 jones
104 1831 tao
    /** get the Value parameter */
105 2068 jones
    public String getValue()
106
    {
107
        return value;
108 1831 tao
    }
109
110
    /** get the path expression parameter */
111 2068 jones
    public String getPathExpression()
112
    {
113
        return pathexpr;
114 1831 tao
    }
115 2068 jones
116
    /** get the percentage count for one query term */
117 1831 tao
    public int getPercentageSymbolCount()
118
    {
119 2068 jones
        return countPercentageSearchItem;
120 1831 tao
    }
121
122
    /**
123
     * create a SQL serialization of the query that this instance represents
124
     */
125 2068 jones
    public String printSQL(boolean useXMLIndex)
126
    {
127
        StringBuffer self = new StringBuffer();
128 1831 tao
129 2068 jones
        // Uppercase the search string if case match is not important
130
        String casevalue = null;
131
        String nodedataterm = null;
132 2654 sgarg
        boolean notEqual = false;
133 2068 jones
        if (casesensitive) {
134
            nodedataterm = "nodedata";
135
            casevalue = value;
136
        } else {
137
            nodedataterm = "UPPER(nodedata)";
138
            casevalue = value.toUpperCase();
139
        }
140 1831 tao
141 2068 jones
        // Add appropriate wildcards to search string
142
        String searchexpr = null;
143
        if (searchmode.equals("starts-with")) {
144 3053 jones
            searchexpr = nodedataterm + " LIKE '" + casevalue + "%' ";
145 2068 jones
        } else if (searchmode.equals("ends-with")) {
146 3053 jones
            searchexpr = nodedataterm + " LIKE '%" + casevalue + "' ";
147 2068 jones
        } else if (searchmode.equals("contains")) {
148
            if (!casevalue.equals("%")) {
149 3053 jones
                searchexpr = nodedataterm + " LIKE '%" + casevalue + "%' ";
150 2068 jones
            } else {
151 3053 jones
                searchexpr = nodedataterm + " LIKE '" + casevalue + "' ";
152 2068 jones
                // find percentage symbol
153
                percentageSymbol = true;
154
            }
155
        } else if (searchmode.equals("not-contains")) {
156 2654 sgarg
        	notEqual = true;
157 3053 jones
            searchexpr = nodedataterm + " LIKE '%" + casevalue + "%' ";
158 2068 jones
        } else if (searchmode.equals("equals")) {
159
            searchexpr = nodedataterm + " = '" + casevalue + "' ";
160
        } else if (searchmode.equals("isnot-equal")) {
161 2654 sgarg
        	notEqual = true;
162
            searchexpr = nodedataterm + " = '" + casevalue + "' ";
163 1831 tao
        } else {
164 2068 jones
            String oper = null;
165
            if (searchmode.equals("greater-than")) {
166
                oper = ">";
167 2357 sgarg
                nodedataterm = "nodedatanumerical";
168 2068 jones
            } else if (searchmode.equals("greater-than-equals")) {
169
                oper = ">=";
170 2357 sgarg
                nodedataterm = "nodedatanumerical";
171 2068 jones
            } else if (searchmode.equals("less-than")) {
172
                oper = "<";
173 2357 sgarg
                nodedataterm = "nodedatanumerical";
174 2068 jones
            } else if (searchmode.equals("less-than-equals")) {
175
                oper = "<=";
176 2357 sgarg
                nodedataterm = "nodedatanumerical";
177 2068 jones
            } else {
178
                System.out
179
                        .println("NOT expected case. NOT recognized operator: "
180
                                + searchmode);
181
                return null;
182
            }
183
            try {
184
                // it is number; numeric comparison
185
                // but we need to make sure there is no string in node data
186
                searchexpr = nodedataterm + " " + oper + " "
187 2357 sgarg
                        + new Double(casevalue) + " ";
188 2068 jones
            } catch (NumberFormatException nfe) {
189
                // these are characters; character comparison
190
                searchexpr = nodedataterm + " " + oper + " '" + casevalue
191
                        + "' ";
192
            }
193 1831 tao
        }
194
195 2068 jones
196 2522 sgarg
        // to check xml_path_index can be used
197
        boolean usePathIndex = false;
198
199
        // if pathexpr has been specified in metacat.properties for indexing
200
        if(pathexpr != null){
201
            if(MetaCatUtil.pathsForIndexing.contains(pathexpr)){
202
                usePathIndex = true;
203
            }
204
        }
205
206
        if(usePathIndex){
207
            // using xml_path_index table.....
208 2654 sgarg
        	if(notEqual == true){
209
        		self.append("SELECT DISTINCT docid from xml_path_index WHERE");
210
        		self.append(" docid NOT IN (Select docid FROM xml_path_index WHERE ");
211
        		self.append(searchexpr);
212 3053 jones
        		self.append("AND path LIKE '" + pathexpr + "') ");
213 2654 sgarg
        	} else {
214
        		self.append("SELECT DISTINCT docid FROM xml_path_index WHERE ");
215
        		self.append(searchexpr);
216 3053 jones
        		self.append("AND path LIKE '" + pathexpr + "' ");
217 2654 sgarg
        	}
218 2522 sgarg
219
        } else {
220
            // using xml_nodes and xml_index tables
221
222 2654 sgarg
        	if(notEqual == true){
223
        		self.append("SELECT DISTINCT docid from xml_nodes WHERE");
224
        		self.append(" docid NOT IN (Select docid FROM xml_nodes WHERE ");
225
        	} else {
226 2693 sgarg
        		self.append("(SELECT DISTINCT docid FROM xml_nodes WHERE ");
227 2654 sgarg
        	}
228
        	self.append(searchexpr);
229
230 2522 sgarg
            if (pathexpr != null) {
231
232
                // use XML Index
233
                if (useXMLIndex) {
234
                    if (!hasAttributeInPath(pathexpr)) {
235
                        // without attributes in path
236
                        self.append("AND parentnodeid IN ");
237 2654 sgarg
                    } else {
238 2522 sgarg
                        // has a attribute in path
239
                        String attributeName = QuerySpecification
240 2068 jones
                            .getAttributeName(pathexpr);
241 2522 sgarg
                        self.append(
242
                            "AND nodetype LIKE 'ATTRIBUTE' AND nodename LIKE '"
243
                            + attributeName + "' ");
244
                        // and the path expression includes element content other than
245
                        // just './' or '../'
246
                        if ( (!pathexpr.startsWith(QuerySpecification.
247
                            ATTRIBUTESYMBOL)) &&
248
                            (!pathexpr.startsWith("./" +
249
                                                  QuerySpecification.ATTRIBUTESYMBOL)) &&
250
                            (!pathexpr.startsWith("../" +
251
                                                  QuerySpecification.ATTRIBUTESYMBOL))) {
252 2068 jones
253 2522 sgarg
                            self.append("AND parentnodeid IN ");
254
                            pathexpr = QuerySpecification
255
                                .newPathExpressionWithOutAttribute(pathexpr);
256
                        }
257 2459 cjones
                    }
258 2522 sgarg
                    self.append(
259
                        "(SELECT nodeid FROM xml_index WHERE path LIKE "
260
                        + "'" + pathexpr + "') ");
261 2068 jones
                }
262 2522 sgarg
                else {
263
                    // without using XML Index; using nested statements instead
264
                    self.append("AND parentnodeid IN ");
265
                    self.append(useNestedStatements(pathexpr));
266
                }
267 2068 jones
            }
268 2522 sgarg
            else if ( (value.trim()).equals("%")) {
269
                //if pathexpr is null and search value is %, is a
270
                // percentageSearchItem
271
                // the count number will be increase one
272
                countPercentageSearchItem++;
273 2068 jones
274 2522 sgarg
            }
275 2654 sgarg
            self.append(") ");
276 1831 tao
        }
277
278 2068 jones
        return self.toString();
279 1831 tao
    }
280 2068 jones
281
    /** A method to judge if a path have attribute */
282 1831 tao
    private boolean hasAttributeInPath(String path)
283
    {
284 2068 jones
        if (path.indexOf(QuerySpecification.ATTRIBUTESYMBOL) != -1) {
285
            return true;
286
        } else {
287
            return false;
288
        }
289 1831 tao
    }
290 2068 jones
291 3235 sledge
    /**
292 2068 jones
     * Constraint the query with @pathexp without using the XML Index, but
293
     * nested SQL statements instead. The query migth be slower.
294 2069 jones
    public static String useNestedStatements(String pathexpr)
295 1831 tao
    {
296 3211 berkley
      System.out.println("pathexpr: " + pathexpr);
297 2068 jones
        StringBuffer nestedStmts = new StringBuffer();
298
        Vector nodes = new Vector();
299
        String path = pathexpr;
300
        int inx = 0;
301 1831 tao
302 2068 jones
        do {
303
            inx = path.lastIndexOf("/");
304 1831 tao
305 2068 jones
            nodes.addElement(path.substring(inx + 1));
306
            path = path.substring(0, Math.abs(inx));
307
        } while (inx > 0);
308 1831 tao
309 2068 jones
        // nested statements
310
        int i = 0;
311
        for (i = 0; i < nodes.size() - 1; i++) {
312
            nestedStmts.append("(SELECT nodeid FROM xml_nodes"
313
                    + " WHERE nodename LIKE '" + (String) nodes.elementAt(i)
314 3235 sledge
                    + "' AND parentnodeid IN ");
315 2068 jones
        }
316
        // for the last statement: it is without " AND parentnodeid IN "
317
        nestedStmts.append("(SELECT nodeid FROM xml_nodes"
318
                + " WHERE nodename LIKE '" + (String) nodes.elementAt(i) + "'");
319
        // node.size() number of closing brackets
320
        for (i = 0; i < nodes.size(); i++) {
321
            nestedStmts.append(")");
322
        }
323 1831 tao
324 2068 jones
        return nestedStmts.toString();
325 1831 tao
    }
326 3235 sledge
     */
327
    public static String useNestedStatements(String pathexpr)
328
    {
329
        log.info("useNestedStatements()");
330
        log.info("pathexpr: " + pathexpr);
331
        StringBuffer nestedStmts = new StringBuffer();
332
        String path = pathexpr.trim();
333 1831 tao
334 3235 sledge
        if (path.indexOf('/') == 0)
335
        {
336
            nestedStmts.append("AND parentnodeid = rootnodeid ");
337
            path = path.substring(1).trim();
338
        }
339
340
        do
341
        {
342
            int inx = path.indexOf('/');
343
            int predicateStart = -1;
344
            int predicateEnd;
345
            String node;
346
            Vector predicates = new Vector();
347
348
            // extract predicates
349
            predicateStart = path.indexOf(QuerySpecification.PREDICATE_START, predicateStart + 1);
350
351
            // any predicates in this node?
352
            if (inx != -1 && (predicateStart == -1 || predicateStart > inx))
353
            {
354
                // no
355
                node = path.substring(0, inx).trim();
356
                path = path.substring(inx + 1).trim();
357
            }
358
            else if (predicateStart == -1)
359
            {
360
                // no and it's the last node
361
                node = path;
362
                path = "";
363
            }
364
            else
365
            {
366
                // yes
367
                node = path.substring(0, predicateStart).trim();
368
                path = path.substring(predicateStart);
369
                predicateStart = 0;
370
371
                while (predicateStart == 0)
372
                {
373
                    predicateEnd = path.indexOf(QuerySpecification.PREDICATE_END,
374
                            predicateStart);
375
376
                    if (predicateEnd == -1)
377
                    {
378
                        log.warn("useNestedStatements(): ");
379
                        log.warn("    Invalid path: " + pathexpr);
380
                        return "";
381
                    }
382
383
                    predicates.add(path.substring(1, predicateEnd).trim());
384
                    path = path.substring(predicateEnd + 1).trim();
385
                    inx = path.indexOf('/');
386
                    predicateStart = path.indexOf(QuerySpecification.PREDICATE_START);
387
                }
388
389
                if (inx == 0)
390
                    path = path.substring(1).trim();
391
                else if (!path.equals(""))
392
                {
393
                    log.warn("useNestedStatements(): ");
394
                    log.warn("    Invalid path: " + pathexpr);
395
                    return "";
396
                }
397
            }
398
399
            nestedStmts.insert(0, "' ").insert(0, node).insert(0,
400
                    "(SELECT nodeid FROM xml_nodes WHERE nodename LIKE '");
401
402
            // for the last statement: it is without " AND parentnodeid IN "
403
            if (!path.equals(""))
404
                nestedStmts.insert(0, "AND parentnodeid IN ");
405
406
            if (predicates.size() > 0)
407
            {
408
                for (int n = 0; n < predicates.size(); n++)
409
                {
410
                    String predSQL = predicate2SQL((String) predicates.get(n));
411
412
                    if (predSQL.equals(""))
413
                        return "";
414
415
                    nestedStmts.append(predSQL).append(' ');
416
                }
417
            }
418
419
            nestedStmts.append(") ");
420
        }
421
        while (!path.equals(""));
422
423
        return nestedStmts.toString();
424
    }
425
426 1831 tao
    /**
427 3235 sledge
     *
428
     */
429
    public static String predicate2SQL(String predicate)
430
    {
431
        String path = predicate.trim();
432
        int equals = path.indexOf('=');
433
        String literal = null;
434
435
        if (equals != -1)
436
        {
437
            literal = path.substring(equals + 1).trim();
438
            path = path.substring(0, equals).trim();
439
            int sQuote = literal.indexOf('\'');
440
            int dQuote = literal.indexOf('"');
441
442
            if (sQuote == -1 && dQuote == -1)
443
            {
444
                log.warn("predicate2SQL(): ");
445
                log.warn("    Invalid or unsupported predicate: " + predicate);
446
                return "";
447
            }
448
449
            if (sQuote == -1 &&
450
                (dQuote != 0 ||
451
                 literal.indexOf('"', dQuote + 1) != literal.length() - 1))
452
            {
453
                log.warn("predicate2SQL(): ");
454
                log.warn("    Invalid or unsupported predicate: " + predicate);
455
                return "";
456
            }
457
458
            if (sQuote != 0 ||
459
                literal.indexOf('\'', sQuote + 1) != literal.length() - 1)
460
            {
461
                log.warn("predicate2SQL(): ");
462
                log.warn("    Invalid or unsupported predicate: " + predicate);
463
                return "";
464
            }
465
        }
466
467
        StringBuffer sql = new StringBuffer();
468
        int attribute = path.indexOf('@');
469
470
        if (attribute == -1)
471
        {
472
            if (literal != null)
473
            {
474
                sql.append("AND nodeid IN (SELECT parentnodeid FROM xml_nodes WHERE nodetype = 'TEXT' AND nodedata LIKE ")
475
                    .append(literal).append(")");
476
            }
477
        }
478
        else
479
        {
480
            sql.append(
481
                    "AND nodeid IN (SELECT parentnodeid FROM xml_nodes WHERE nodetype = 'ATTRIBUTE' AND nodename LIKE '")
482
                    .append(path.substring(attribute + 1).trim()).append("' ");
483
484
            if (literal != null)
485
            {
486
                sql.append("AND nodedata LIKE ").append(literal);
487
            }
488
489
            sql.append(")");
490
            path = path.substring(0, attribute).trim();
491
492
            if (path.endsWith("/"))
493
                path = path.substring(0, path.length() - 1).trim();
494
            else
495
            {
496
                if (!path.equals(""))
497
                {
498
                    log.warn("predicate2SQL(): ");
499
                    log.warn("    Invalid or unsupported predicate: " + predicate);
500
                    return "";
501
                }
502
            }
503
        }
504
505
        while (!path.equals(""))
506
        {
507
            int ndx = path.lastIndexOf('/');
508
            int predicateEnd = -1;
509
            int predicateStart;
510
            String node;
511
512
            if (ndx != -1)
513
            {
514
                node = path.substring(ndx + 1).trim();
515
                path = path.substring(0, ndx).trim();
516
            }
517
            else
518
            {
519
                node = path;
520
                path = "";
521
            }
522
523
            if (!node.equals(""))
524
                sql.insert(0, "' ").insert(0, node)
525
                    .insert(0, "(SELECT parentnodeid FROM xml_nodes WHERE nodename LIKE '").append(") ");
526
            else if (!path.equals(""))
527
            {
528
                log.warn("predicate2SQL(): ");
529
                log.warn("    Invalid or unsupported predicate: " + predicate);
530
                return "";
531
            }
532
533
            if (path.equals(""))
534
            {
535
                sql.insert(0,
536
                        node.equals("") ? "AND rootnodeid IN " : "AND nodeid IN ");
537
            }
538
            else
539
            {
540
                sql.append("AND nodeid IN ");
541
            }
542
        }
543
544
        return sql.toString();
545
    }
546
547
    /**
548 1831 tao
     * create a String description of the query that this instance represents.
549
     * This should become a way to get the XML serialization of the query.
550
     */
551 2068 jones
    public String toString()
552
    {
553 1831 tao
554 2068 jones
        return this.printSQL(true);
555 1831 tao
    }
556 3224 tao
557
    /**
558
     * Compare two query terms to see if they have same search value.
559
     * @param term
560
     * @return
561
     */
562
    public boolean hasSameSearchValue(QueryTerm term)
563
    {
564
    	boolean same = false;
565
    	if (term != null)
566
    	{
567
    		String searchValue = term.getValue();
568
    		if (searchValue != null && this.value != null)
569
    		{
570
    			if (searchValue.equalsIgnoreCase(this.value))
571
    			{
572
    				same = true;
573
    			}
574
    		}
575
    	}
576
    	return same;
577
    }
578 2068 jones
}