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
 *
11
 *   '$Author: tao $'
12
 *     '$Date: 2008-04-09 18:30:29 -0700 (Wed, 09 Apr 2008) $'
13
 * '$Revision: 3786 $'
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
import org.apache.log4j.Logger;
34

    
35
/** a utility class that represents a single term in a query */
36
public class QueryTerm
37
{
38
    private static Logger log = Logger.getLogger(QueryTerm.class);
39

    
40
    private boolean casesensitive = false;
41

    
42
    private String searchmode = null;
43

    
44
    private String value = null;
45

    
46
    private String pathexpr = null;
47

    
48
    private boolean percentageSymbol = false;
49

    
50
    private int countPercentageSearchItem = 0;
51
    
52
    private boolean inUnionGroup = false;
53
    
54
    public static final String CONTAINS = "contains";
55
    
56
    public static final String EQUALS = "equals";
57

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

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

    
98
    /** determine if the QueryTerm is case sensitive */
99
    public boolean isCaseSensitive()
100
    {
101
        return casesensitive;
102
    }
103

    
104
    /** get the searchmode parameter */
105
    public String getSearchMode()
106
    {
107
        return searchmode;
108
    }
109

    
110
    /** get the Value parameter */
111
    public String getValue()
112
    {
113
        return value;
114
    }
115

    
116
    /** get the path expression parameter */
117
    public String getPathExpression()
118
    {
119
        return pathexpr;
120
    }
121

    
122
    /** get the percentage count for one query term */
123
    public int getPercentageSymbolCount()
124
    {
125
        return countPercentageSearchItem;
126
    }
127
    
128
    /**
129
     * Set the query term in a union group
130
     * @param inUnionGroup
131
     */
132
    public void setInUnionGroup (boolean inUnionGroup)
133
    {
134
    	this.inUnionGroup = inUnionGroup;
135
    }
136
    
137
    /**
138
     * If this query group in Union group
139
     * @return
140
     */
141
    public boolean isInUnionGroup()
142
    {
143
    	return this.inUnionGroup;
144
    }
145

    
146
    /**
147
     * create a SQL serialization of the query that this instance represents
148
     */
149
    public String printSQL(boolean useXMLIndex)
150
    {
151
        StringBuffer self = new StringBuffer();
152

    
153
        // Uppercase the search string if case match is not important
154
        String casevalue = null;
155
        String nodedataterm = null;
156
        boolean notEqual = false;
157
        if (casesensitive) {
158
            nodedataterm = "nodedata";
159
            casevalue = value;
160
        } else {
161
            nodedataterm = "UPPER(nodedata)";
162
            casevalue = value.toUpperCase();
163
        }
164

    
165
        // Add appropriate wildcards to search string
166
        String searchexpr = null;
167
        if (searchmode.equals("starts-with")) {
168
            searchexpr = nodedataterm + " LIKE '" + casevalue + "%' ";
169
        } else if (searchmode.equals("ends-with")) {
170
            searchexpr = nodedataterm + " LIKE '%" + casevalue + "' ";
171
        } else if (searchmode.equals("contains")) {
172
            if (!casevalue.equals("%")) {
173
                searchexpr = nodedataterm + " LIKE '%" + casevalue + "%' ";
174
            } else {
175
                searchexpr = nodedataterm + " LIKE '" + casevalue + "' ";
176
                // find percentage symbol
177
                percentageSymbol = true;
178
            }
179
        } else if (searchmode.equals("not-contains")) {
180
        	notEqual = true;
181
            searchexpr = nodedataterm + " LIKE '%" + casevalue + "%' ";
182
        } else if (searchmode.equals("equals")) {
183
            searchexpr = nodedataterm + " = '" + casevalue + "' ";
184
        } else if (searchmode.equals("isnot-equal")) {
185
        	notEqual = true;
186
            searchexpr = nodedataterm + " = '" + casevalue + "' ";
187
        } else {
188
            String oper = null;
189
            if (searchmode.equals("greater-than")) {
190
                oper = ">";
191
                nodedataterm = "nodedatanumerical";
192
            } else if (searchmode.equals("greater-than-equals")) {
193
                oper = ">=";
194
                nodedataterm = "nodedatanumerical";
195
            } else if (searchmode.equals("less-than")) {
196
                oper = "<";
197
                nodedataterm = "nodedatanumerical";
198
            } else if (searchmode.equals("less-than-equals")) {
199
                oper = "<=";
200
                nodedataterm = "nodedatanumerical";
201
            } else {
202
                System.out
203
                        .println("NOT expected case. NOT recognized operator: "
204
                                + searchmode);
205
                return null;
206
            }
207
            try {
208
                // it is number; numeric comparison
209
                // but we need to make sure there is no string in node data
210
                searchexpr = nodedataterm + " " + oper + " "
211
                        + new Double(casevalue) + " ";
212
            } catch (NumberFormatException nfe) {
213
                // these are characters; character comparison
214
                searchexpr = nodedataterm + " " + oper + " '" + casevalue
215
                        + "' ";
216
            }
217
        }
218

    
219

    
220
        // to check xml_path_index can be used
221
        boolean usePathIndex = false;
222

    
223
        // if pathexpr has been specified in metacat.properties for indexing
224
        if(pathexpr != null){
225
            if(MetaCatUtil.pathsForIndexing.contains(pathexpr)){
226
                usePathIndex = true;
227
            }
228
        }
229
        
230
        
231
        if(usePathIndex){
232
            // using xml_path_index table.....
233
        	if(notEqual == true ){
234
        		if (!inUnionGroup)
235
        		{
236
        			self.append("SELECT DISTINCT docid from xml_path_index WHERE");
237
        			self.append(" docid NOT IN (Select docid FROM xml_path_index WHERE ");
238
        			self.append(searchexpr);
239
        			self.append("AND path LIKE '" + pathexpr + "') ");
240
        		}
241
        		else
242
        		{
243
        			//if this is in union group we need to use "OR" to modify query
244
        			self.append("("+searchexpr);
245
        			self.append("AND path LIKE '" + pathexpr + "') ");
246
        		}
247
        	} else {
248
        		if (!inUnionGroup)
249
        		{
250
        			self.append("SELECT DISTINCT docid FROM xml_path_index WHERE ");
251
        			self.append(searchexpr);
252
        			self.append("AND path LIKE '" + pathexpr + "' ");
253
        		}
254
        		else
255
        		{
256
        			//if this is in union group we need to use "OR" to modify query
257
        			self.append("("+searchexpr);
258
        			self.append("AND path LIKE '" + pathexpr + "') ");
259
        		}
260
        	}
261

    
262
        } else {
263
            // using xml_nodes and xml_index tables
264

    
265
        	if(notEqual == true){
266
        		self.append("SELECT DISTINCT docid from xml_nodes WHERE");
267
        		self.append(" docid NOT IN (Select docid FROM xml_nodes WHERE ");
268
        	} else {
269
        		self.append("(SELECT DISTINCT docid FROM xml_nodes WHERE ");
270
        	}
271
        	self.append(searchexpr);
272
        	
273
            if (pathexpr != null) {
274
             String path = pathexpr;
275
                // use XML Index
276
                if (useXMLIndex) {
277
                    if (!hasAttributeInPath(pathexpr)) {
278
                        // without attributes in path
279
                        self.append("AND parentnodeid IN ");
280
                    } else {
281
                        // has a attribute in path
282
                        String attributeName = QuerySpecification
283
                            .getAttributeName(pathexpr);
284
                        self.append(
285
                            "AND nodetype LIKE 'ATTRIBUTE' AND nodename LIKE '"
286
                            + attributeName + "' ");
287
                        // and the path expression includes element content other than
288
                        // just './' or '../'
289
                        if ( (!pathexpr.startsWith(QuerySpecification.
290
                            ATTRIBUTESYMBOL)) &&
291
                            (!pathexpr.startsWith("./" +
292
                                                  QuerySpecification.ATTRIBUTESYMBOL)) &&
293
                            (!pathexpr.startsWith("../" +
294
                                                  QuerySpecification.ATTRIBUTESYMBOL))) {
295

    
296
                            self.append("AND parentnodeid IN ");
297
                            path = QuerySpecification
298
                                .newPathExpressionWithOutAttribute(pathexpr);
299
                        }
300
                    }
301
                    self.append(
302
                        "(SELECT nodeid FROM xml_index WHERE path LIKE "
303
                        + "'" + path + "') ");
304
                }
305
                else {
306
                    // without using XML Index; using nested statements instead
307
                    //self.append("AND parentnodeid IN ");
308
                	self.append("AND ");
309
                    self.append(useNestedStatements(pathexpr));
310
                }
311
            }
312
            else if ( (value.trim()).equals("%")) {
313
                //if pathexpr is null and search value is %, is a
314
                // percentageSearchItem
315
                // the count number will be increase one
316
                countPercentageSearchItem++;
317

    
318
            }
319
            self.append(") ");
320
        }
321

    
322
        return self.toString();
323
    }
324

    
325
    /** A method to judge if a path have attribute */
326
    private boolean hasAttributeInPath(String path)
327
    {
328
        if (path.indexOf(QuerySpecification.ATTRIBUTESYMBOL) != -1) {
329
            return true;
330
        } else {
331
            return false;
332
        }
333
    }
334

    
335
   
336
    public static String useNestedStatements(String pathexpr)
337
    {
338
        log.info("useNestedStatements()");
339
        log.info("pathexpr: " + pathexpr);
340
        String elementPrefix = " parentnodeid IN ";
341
        String attributePrefix  =  " nodeid IN ";
342
        boolean lastOneIsAttribute = false;
343
        StringBuffer nestedStmts = new StringBuffer();
344
        String path = pathexpr.trim();
345
        String sql = "";
346

    
347
        if (path.indexOf('/') == 0)
348
        {
349
            nestedStmts.append("AND parentnodeid = rootnodeid ");
350
            path = path.substring(1).trim();
351
        }
352

    
353
        do
354
        {
355
            int inx = path.indexOf('/');
356
            int predicateStart = -1;
357
            int predicateEnd;
358
            String node;
359
            Vector predicates = new Vector();
360

    
361
            // extract predicates
362
            predicateStart = path.indexOf(QuerySpecification.PREDICATE_START, predicateStart + 1);
363

    
364
            // any predicates in this node?
365
            if (inx != -1 && (predicateStart == -1 || predicateStart > inx))
366
            {
367
                // no
368
                node = path.substring(0, inx).trim();
369
                path = path.substring(inx + 1).trim();
370
            }
371
            else if (predicateStart == -1)
372
            {
373
                // no and it's the last node
374
                node = path;
375
                if (node != null && node.indexOf(QuerySpecification.ATTRIBUTESYMBOL) != -1)
376
                {
377
                	lastOneIsAttribute = true;
378
                	node = removeAttributeSymbol(node);
379
                }
380
                path = "";
381
            }
382
            else
383
            {
384
                // yes
385
                node = path.substring(0, predicateStart).trim();
386
                path = path.substring(predicateStart);
387
                predicateStart = 0;
388

    
389
                while (predicateStart == 0)
390
                {
391
                    predicateEnd = path.indexOf(QuerySpecification.PREDICATE_END,
392
                            predicateStart);
393

    
394
                    if (predicateEnd == -1)
395
                    {
396
                        log.warn("useNestedStatements(): ");
397
                        log.warn("    Invalid path: " + pathexpr);
398
                        return "";
399
                    }
400

    
401
                    predicates.add(path.substring(1, predicateEnd).trim());
402
                    path = path.substring(predicateEnd + 1).trim();
403
                    inx = path.indexOf('/');
404
                    predicateStart = path.indexOf(QuerySpecification.PREDICATE_START);
405
                }
406

    
407
                if (inx == 0)
408
                    path = path.substring(1).trim();
409
                else if (!path.equals(""))
410
                {
411
                    log.warn("useNestedStatements(): ");
412
                    log.warn("    Invalid path: " + pathexpr);
413
                    return "";
414
                }
415
            }
416

    
417
            nestedStmts.insert(0, "' ").insert(0, node).insert(0,
418
                    "(SELECT nodeid FROM xml_nodes WHERE nodename LIKE '");
419

    
420
            // for the last statement: it is without " AND parentnodeid IN "
421
            if (!path.equals(""))
422
                nestedStmts.insert(0, "AND parentnodeid IN ");
423

    
424
            if (predicates.size() > 0)
425
            {
426
                for (int n = 0; n < predicates.size(); n++)
427
                {
428
                    String predSQL = predicate2SQL((String) predicates.get(n));
429

    
430
                    if (predSQL.equals(""))
431
                        return "";
432

    
433
                    nestedStmts.append(predSQL).append(' ');
434
                }
435
            }
436

    
437
            nestedStmts.append(") ");
438
        }
439
        while (!path.equals(""));
440
        if (lastOneIsAttribute)
441
        {
442
        	sql = attributePrefix+nestedStmts.toString();
443
        }
444
        else
445
        {
446
        	sql = elementPrefix+nestedStmts.toString();
447
        }
448
        return sql;
449
    }
450
    
451
    
452
    /*
453
     * Removes @ symbol from path. For example, if path is @entity, entity will be returned.
454
     * If path is entity, entity will be returned. 
455
     */
456
    private static String removeAttributeSymbol(String path)
457
    {
458
    	String newPath  ="";
459
    	log.debug("Original string before removing @ is " + path);
460
    	if (path != null)
461
    	{
462
    		
463
    		int attribute = path.indexOf(QuerySpecification.ATTRIBUTESYMBOL);
464
    		if (attribute != -1)
465
    		{
466
    			// has attribute symbol. Reomve it and return the remained part. 
467
    			try
468
    			{
469
    		         newPath = path.substring(attribute + 1).trim();
470
    			}
471
    			catch (Exception e)
472
    			{
473
    				newPath = path;
474
    			}
475
    		}
476
    		else
477
    		{
478
    			// doesn't have attribute symbol. Return original string
479
    			newPath = path;
480
    		}
481
    	}
482
    	else
483
    	{
484
    		// if is null, return null;
485
    		newPath = path;
486
    	}
487
    	log.debug("String after removing @ is " + newPath);
488
    	return newPath;
489
    	
490
    }
491

    
492
    /**
493
     * 
494
     */
495
    public static String predicate2SQL(String predicate)
496
    {
497
        String path = predicate.trim();
498
        int equals = path.indexOf('=');
499
        String literal = null;
500

    
501
        if (equals != -1)
502
        {
503
            literal = path.substring(equals + 1).trim();
504
            path = path.substring(0, equals).trim();
505
            int sQuote = literal.indexOf('\'');
506
            int dQuote = literal.indexOf('"');
507

    
508
            if (sQuote == -1 && dQuote == -1)
509
            {
510
                log.warn("predicate2SQL(): ");
511
                log.warn("    Invalid or unsupported predicate: " + predicate);
512
                return "";
513
            }
514

    
515
            if (sQuote == -1 &&
516
                (dQuote != 0 ||
517
                 literal.indexOf('"', dQuote + 1) != literal.length() - 1))
518
            {
519
                log.warn("predicate2SQL(): ");
520
                log.warn("    Invalid or unsupported predicate: " + predicate);
521
                return "";
522
            }
523

    
524
            if (sQuote != 0 ||
525
                literal.indexOf('\'', sQuote + 1) != literal.length() - 1)
526
            {
527
                log.warn("predicate2SQL(): ");
528
                log.warn("    Invalid or unsupported predicate: " + predicate);
529
                return "";
530
            }
531
        }
532

    
533
        StringBuffer sql = new StringBuffer();
534
        int attribute = path.indexOf('@');
535

    
536
        if (attribute == -1)
537
        {
538
            if (literal != null)
539
            {
540
                sql.append("AND nodeid IN (SELECT parentnodeid FROM xml_nodes WHERE nodetype = 'TEXT' AND nodedata LIKE ")
541
                    .append(literal).append(")");
542
            }
543
        }
544
        else
545
        {
546
            sql.append(
547
                    "AND nodeid IN (SELECT parentnodeid FROM xml_nodes WHERE nodetype = 'ATTRIBUTE' AND nodename LIKE '")
548
                    .append(path.substring(attribute + 1).trim()).append("' ");
549

    
550
            if (literal != null)
551
            {
552
                sql.append("AND nodedata LIKE ").append(literal);
553
            }
554

    
555
            sql.append(")");
556
            path = path.substring(0, attribute).trim();
557

    
558
            if (path.endsWith("/"))
559
                path = path.substring(0, path.length() - 1).trim();
560
            else
561
            {
562
                if (!path.equals(""))
563
                {
564
                    log.warn("predicate2SQL(): ");
565
                    log.warn("    Invalid or unsupported predicate: " + predicate);
566
                    return "";
567
                }
568
            }
569
        }
570

    
571
        while (!path.equals(""))
572
        {
573
            int ndx = path.lastIndexOf('/');
574
            int predicateEnd = -1;
575
            int predicateStart;
576
            String node;
577

    
578
            if (ndx != -1)
579
            {
580
                node = path.substring(ndx + 1).trim();
581
                path = path.substring(0, ndx).trim();
582
            }
583
            else
584
            {
585
                node = path;
586
                path = "";
587
            }
588

    
589
            if (!node.equals(""))
590
                sql.insert(0, "' ").insert(0, node)
591
                    .insert(0, "(SELECT parentnodeid FROM xml_nodes WHERE nodename LIKE '").append(") ");
592
            else if (!path.equals(""))
593
            {
594
                log.warn("predicate2SQL(): ");
595
                log.warn("    Invalid or unsupported predicate: " + predicate);
596
                return "";
597
            }
598

    
599
            if (path.equals(""))
600
            {
601
                sql.insert(0,
602
                        node.equals("") ? "AND rootnodeid IN " : "AND nodeid IN ");
603
            }
604
            else
605
            {
606
                sql.append("AND nodeid IN ");
607
            }
608
        }
609

    
610
        return sql.toString();
611
    }
612

    
613
    /**
614
     * create a String description of the query that this instance represents.
615
     * This should become a way to get the XML serialization of the query.
616
     */
617
    public String toString()
618
    {
619

    
620
        return this.printSQL(true);
621
    }
622
    
623
    /**
624
     * Compare two query terms to see if they have same search value.
625
     * @param term
626
     * @return
627
     */
628
    public boolean hasSameSearchValue(QueryTerm term)
629
    {
630
    	boolean same = false;
631
    	if (term != null)
632
    	{
633
    		String searchValue = term.getValue();
634
    		if (searchValue != null && this.value != null)
635
    		{
636
    			if (searchValue.equalsIgnoreCase(this.value))
637
    			{
638
    				same = true;
639
    			}
640
    		}
641
    	}
642
    	return same;
643
    }
644
}
(55-55/66)