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