Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2005 University of New Mexico and the 
4
 *             Regents of the University of California and the
5
 *             National Center for Ecological Analysis and Synthesis
6
 *
7
 *   '$Author: costa $'
8
 *     '$Date: 2005-11-16 09:57:33 -0800 (Wed, 16 Nov 2005) $'
9
 * '$Revision: 2741 $'
10
 *
11
 * This program is free software; you can redistribute it and/or modify
12
 * it under the terms of the GNU General Public License as published by
13
 * the Free Software Foundation; either version 2 of the License, or
14
 * (at your option) any later version.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 * along with this program; if not, write to the Free Software
23
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
 */
25

    
26
package edu.ucsb.nceas.metacat.advancedsearch;
27

    
28
import java.io.Reader;
29
import java.io.StringReader;
30
import java.util.ArrayList;
31
import java.util.StringTokenizer;
32

    
33
import edu.ucsb.nceas.metacat.client.*;
34
import edu.ucsb.nceas.utilities.*;
35

    
36
/**
37
 * @author dcosta
38
 * 
39
 * AdvancedSearch class constructs queries that use the pathquery feature of 
40
 * Metacat. It can execute either an advanced search, where the user fills in
41
 * fields in a web form, or a simple search on a string.
42
 */
43
public class AdvancedSearch  {
44

    
45
  // Object variables
46
  private AdvancedSearchBean advancedSearchBean = null;
47
  private String caseSensitive;
48
  private final String globalOperator;
49
  private boolean hasSubjectSearch = false;
50
  private boolean hasAuthorSearch = false;
51
  private boolean hasSpatialSearch = false;
52
  private boolean hasTaxonomicSearch = false;
53
  private boolean hasTemporalSearch = false;
54
  private boolean hasSiteFilter = false;
55
  private int indentLevel = 2;
56
  private boolean isCaseSensitive = false;
57
  private AdvancedSearchPathQuery pathQuery;
58
  private AdvancedSearchQueryGroup queryGroup;
59
  private String queryString;
60
  private String site;
61
  private final String title = "Advanced Search";
62

    
63

    
64
  /**
65
   * Constructor. Used when the user has filled in an Advanced Search form.
66
   * The property values are contained in the advancedSearchBean object.
67
   * For a simple search, the advancedSearchBean object is passed as null.
68
   * 
69
   * @param advancedSearchBean   An AdvancedSearch bean.
70
   */
71
  public AdvancedSearch(final AdvancedSearchBean advancedSearchBean) {
72
    int allAny = AdvancedSearchBean.MATCH_ALL;
73
    String indent = getIndent(indentLevel * 1);
74
    final String operator;
75
    
76
    if (advancedSearchBean != null) {
77
      allAny = advancedSearchBean.getFormAllAny();
78
      this.isCaseSensitive = advancedSearchBean.isCaseSensitive();
79
      site = advancedSearchBean.getSiteValue();
80
    }
81

    
82
    if (allAny == AdvancedSearchBean.MATCH_ALL) {
83
      globalOperator = "INTERSECT";
84
    }
85
    else {
86
      globalOperator = "UNION";
87
    }
88
    
89
    if (isCaseSensitive == true) {
90
      this.caseSensitive = "true";
91
    }
92
    else {
93
      this.caseSensitive = "false";
94
    }
95

    
96
    this.queryGroup = new AdvancedSearchQueryGroup(globalOperator, indent);
97
    this.pathQuery = new AdvancedSearchPathQuery(title, queryGroup, indent);
98
    this.advancedSearchBean = advancedSearchBean;
99
  }
100
  
101

    
102
  /**
103
   * Adds a string to an ArrayList of terms. An auxiliary method to the
104
   * parseTermsAdvanced() method.
105
   * 
106
   * @param terms      ArrayList of strings.
107
   * @param term       the new string to add to the ArrayList, but only if
108
   *                   it isn't an empty string.
109
   */
110
  private void addTerm(ArrayList terms, final StringBuffer term) {
111
    final String s = term.toString().trim();
112
      
113
    if (s.length() > 0) {
114
      terms.add(s);
115
    }
116
  }
117

    
118

    
119
  /**
120
   * A full subject query searches the title, abstract, and keyword sections of
121
   * the document. Individual searches on these sections is also supported.
122
   */
123
  private void buildQuerySubject() {
124
    int allAny = advancedSearchBean.getSubjectAllAny();
125
    String emlField;
126
    String indent;
127
    final String innerOperator = "UNION";
128
    AdvancedSearchQueryGroup innerQuery = null;
129
    final String outerOperator;
130
    AdvancedSearchQueryGroup outerQuery;
131
    AdvancedSearchQueryTerm qt;
132
    String searchMode;
133
    final String subjectField = advancedSearchBean.getSubjectField();
134
    final int subjectQueryType = advancedSearchBean.getSubjectQueryType();
135
    String term;
136
    ArrayList terms;
137
    String value = advancedSearchBean.getSubjectValue();
138
 
139
    if ((value != null) && (!(value.equals("")))) {
140
      hasSubjectSearch = true;
141

    
142
      if (allAny == AdvancedSearchBean.MATCH_ALL) {
143
        outerOperator = "INTERSECT";
144
      }
145
      else {
146
        outerOperator = "UNION";
147
      }
148

    
149
      indent = getIndent(indentLevel * 2);
150
      outerQuery = new AdvancedSearchQueryGroup(outerOperator, indent);
151
      terms = parseTermsAdvanced(value);
152
      searchMode = metacatSearchMode(subjectQueryType);
153
      
154
      for (int i = 0; i < terms.size(); i++) {
155
        term = (String) terms.get(i);
156
        indent = getIndent(indentLevel * 3);
157
        innerQuery = new AdvancedSearchQueryGroup(innerOperator, indent);
158
        indent = getIndent(indentLevel * 4);
159
            
160
        if (subjectField.equals("ALL") || subjectField.equals("TITLE")) {
161
          emlField = "dataset/title";
162
          qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
163
                                           term, indent);
164
          innerQuery.addQueryTerm(qt);
165
        }
166

    
167
        if (subjectField.equals("ALL") || subjectField.equals("ABSTRACT")) {
168
          emlField = "dataset/abstract/para";
169
          qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
170
                                           term, indent);
171
          innerQuery.addQueryTerm(qt);
172

    
173
          emlField = "dataset/abstract/section/para";
174
          qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
175
                                           term, indent);
176
          innerQuery.addQueryTerm(qt);
177
        }
178

    
179
        if (subjectField.equals("ALL") || subjectField.equals("KEYWORDS")) {
180
          emlField = "keywordSet/keyword";
181
          qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
182
                                           term, indent);
183
          innerQuery.addQueryTerm(qt);
184
        }
185
      
186
        outerQuery.addQueryGroup(innerQuery);
187
      }
188

    
189
      // Minimize the number of query groups that get created, depending on
190
      // which criteria the user specified.
191
      //
192
      if (terms.size() > 1) {
193
        queryGroup.addQueryGroup(outerQuery);
194
      }
195
      else if (terms.size() == 1){
196
        queryGroup.addQueryGroup(innerQuery);
197
      }
198
    }
199

    
200
  }
201
  
202

    
203
  /**
204
   * An author query will search the creator/individualName/surName field, the
205
   * creator/organizationName field, or an intersection of both fields.
206
   */
207
  private void buildQueryAuthor() {
208
    boolean addQueryGroup = false;
209
    final int creatorSurnameQueryType = 
210
                                advancedSearchBean.getCreatorSurnameQueryType();
211
    final int creatorOrganizationQueryType = 
212
                           advancedSearchBean.getCreatorOrganizationQueryType();
213
    String emlField;
214
    String indent = getIndent(indentLevel * 2);
215
    AdvancedSearchQueryGroup qg = 
216
                           new AdvancedSearchQueryGroup(globalOperator, indent);
217
    AdvancedSearchQueryTerm qt;
218
    String searchMode;
219
    String value = advancedSearchBean.getCreatorSurname();
220

    
221
    indent = getIndent(indentLevel * 3);
222
    if ((value != null) && (!(value.equals("")))) {
223
      emlField = "dataset/creator/individualName/surName";
224
      searchMode = metacatSearchMode(creatorSurnameQueryType);
225
      qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
226
                                       value, indent);
227
      qg.addQueryTerm(qt);        
228
      addQueryGroup = true;
229
    }
230

    
231
    value = advancedSearchBean.getCreatorOrganization();
232
      
233
    if ((value != null) && (!(value.equals("")))) {
234
      emlField = "creator/organizationName";
235
      searchMode = metacatSearchMode(creatorOrganizationQueryType);
236
      qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
237
                                       value, indent);
238
      qg.addQueryTerm(qt);        
239
      addQueryGroup = true;
240
    }
241
    
242
    if (addQueryGroup) {      
243
      hasAuthorSearch = true;
244
      queryGroup.addQueryGroup(qg);
245
    }
246
  }
247
  
248

    
249
  /**
250
   * Two kinds of spatial searches are supported. The first is on a specific
251
   * named location. The second is on north/south/east/west bounding
252
   * coordinates. An intersection of both searches is done if the user
253
   * specifies search values for a named location as well as one or more
254
   * bounding coordinates.
255
   */
256
  private void buildQuerySpatialCriteria() {
257
    boolean addBoundingValues = false;
258
    boolean addGeographicDescription = false;
259
    final boolean boundaryContained = advancedSearchBean.isBoundaryContained();
260
    String emlField;
261
    final String operator = "INTERSECT";
262
    String indent = getIndent(indentLevel * 2);
263
    AdvancedSearchQueryGroup qgBounding;
264
    AdvancedSearchQueryGroup qgGeographicDescription;
265
    AdvancedSearchQueryGroup qgSpatial;
266
    AdvancedSearchQueryTerm qt;
267
    String searchMode;
268
    String value, northValue, southValue, eastValue, westValue;
269

    
270
    qgSpatial = new AdvancedSearchQueryGroup(globalOperator, indent);
271
    indent = getIndent(indentLevel * 3);
272
    qgBounding = new AdvancedSearchQueryGroup(operator, indent);
273
    qgGeographicDescription = new AdvancedSearchQueryGroup(operator, indent);
274
    indent = getIndent(indentLevel * 4);
275

    
276
    /* Check whether user specified a named location. */
277
    value = advancedSearchBean.getLocationName();   
278

    
279
    if ((value != null) && (!(value.equals("")))) {
280
      searchMode = "contains";
281
      emlField = "geographicCoverage/geographicDescription";
282
      qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
283
                                       value, indent);
284
      qgGeographicDescription.addQueryTerm(qt);
285
      addGeographicDescription = true;
286
    }
287

    
288
    /*
289
     * If the user selects the boundaryContained checkbox, use the following
290
     * logical expression. N, S, E, and W are the boundaries of the bounding
291
     * box, while N', S', E', and W' are the boundaries specified in a given
292
     * EML document:
293
     *              (N' <= N) && (S' >= S) && (E' <= E) && (W' >= W)
294
     */
295
    if (boundaryContained) {
296
      northValue = advancedSearchBean.getNorthBound();
297
      if ((northValue != null) && (!(northValue.equals("")))) {
298
        emlField = 
299
               "geographicCoverage/boundingCoordinates/northBoundingCoordinate";
300
        searchMode = "less-than-equals";
301
        qt=new AdvancedSearchQueryTerm(searchMode,caseSensitive,emlField, 
302
                                       northValue, indent);
303
        qgBounding.addQueryTerm(qt);        
304
        addBoundingValues = true;
305
      }
306

    
307
      southValue = advancedSearchBean.getSouthBound();
308
      if ((southValue != null) && (!(southValue.equals("")))) {
309
        emlField = 
310
               "geographicCoverage/boundingCoordinates/southBoundingCoordinate";
311
        searchMode = "greater-than-equals";
312
        qt=new AdvancedSearchQueryTerm(searchMode,caseSensitive,emlField, 
313
                                       southValue, indent);
314
        qgBounding.addQueryTerm(qt);        
315
        addBoundingValues = true;
316
      }
317

    
318
      eastValue = advancedSearchBean.getEastBound();
319
      if ((eastValue != null) && (!(eastValue.equals("")))) {
320
        emlField =
321
                "geographicCoverage/boundingCoordinates/eastBoundingCoordinate";
322
        searchMode = "less-than-equals";
323
        qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
324
                                         eastValue, indent);
325
        qgBounding.addQueryTerm(qt);        
326
        addBoundingValues = true;
327
      }
328

    
329
      westValue = advancedSearchBean.getWestBound();
330
      if ((westValue != null) && (!(westValue.equals("")))) {
331
        emlField =
332
                "geographicCoverage/boundingCoordinates/westBoundingCoordinate";
333
        searchMode = "greater-than-equals";
334
        qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
335
                                         westValue, indent);
336
        qgBounding.addQueryTerm(qt);        
337
        addBoundingValues = true;
338
      }
339
    }
340
   /*
341
    * Else, if the user does not select the boundaryContained checkbox, use the 
342
    * following logical expression. N, S, E, and W are the boundaries of the 
343
    * bounding box, while N', S', E', and W' are the boundaries specified in a 
344
    * given EML document:
345
    *              (N' > S) && (S' < N) && (E' > W) && (W' < E)
346
    */
347
    else {     
348
      northValue = advancedSearchBean.getNorthBound();
349
      if ((northValue != null) && (!(northValue.equals("")))) {
350
        emlField =
351
               "geographicCoverage/boundingCoordinates/southBoundingCoordinate";
352
        searchMode = "less-than";
353
        qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
354
                                         northValue, indent);
355
        qgBounding.addQueryTerm(qt);        
356
        addBoundingValues = true;
357
      }
358

    
359
      southValue = advancedSearchBean.getSouthBound();
360
      if ((southValue != null) && (!(southValue.equals("")))) {
361
        emlField = 
362
               "geographicCoverage/boundingCoordinates/northBoundingCoordinate";
363
        searchMode = "greater-than";
364
        qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
365
                                         southValue, indent);
366
        qgBounding.addQueryTerm(qt);        
367
        addBoundingValues = true;
368
      }
369

    
370
      eastValue = advancedSearchBean.getEastBound();
371
      if ((eastValue != null) && (!(eastValue.equals("")))) {
372
        emlField =
373
                "geographicCoverage/boundingCoordinates/westBoundingCoordinate";
374
        searchMode = "less-than";
375
        qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
376
                                         eastValue, indent);
377
        qgBounding.addQueryTerm(qt);        
378
        addBoundingValues = true;
379
      }
380

    
381
      westValue = advancedSearchBean.getWestBound();
382
      if ((westValue != null) && (!(westValue.equals("")))) {
383
        emlField =
384
                "geographicCoverage/boundingCoordinates/eastBoundingCoordinate";
385
        searchMode = "greater-than";
386
        qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
387
                                         westValue, indent);
388
        qgBounding.addQueryTerm(qt);        
389
        addBoundingValues = true;
390
      }
391
    }
392

    
393
    // Minimize the number of query groups that get created, depending on
394
    // which criteria the user specified.
395
    //
396
    if (addBoundingValues || addGeographicDescription) {      
397
      hasSpatialSearch = true;
398
      
399
      if (addBoundingValues && addGeographicDescription) { 
400
        qgSpatial.addQueryGroup(qgBounding);
401
        qgSpatial.addQueryGroup(qgGeographicDescription);
402
        queryGroup.addQueryGroup(qgSpatial);
403
      }    
404
      else if (addBoundingValues) {
405
        queryGroup.addQueryGroup(qgBounding);
406
      }
407
      else {
408
        queryGroup.addQueryGroup(qgGeographicDescription);
409
      }
410
    }
411
    
412
  }
413
  
414

    
415
  /**
416
   * Two kinds of temporal searches are supported. The first is on a named
417
   * time scale. The second is on a specific start date and/or end date.
418
   */
419
  private void buildQueryTemporalCriteria() {
420
    boolean addQueryGroup = false;
421
    boolean addQueryGroupDates = false;
422
    boolean addQueryGroupNamed = false;
423
    final String dateField = advancedSearchBean.getDateField();
424
    String emlField;
425
    final String operator = "INTERSECT";
426
    String indent = getIndent(indentLevel * 2);
427
    final int namedTimescaleQueryType = 
428
                                advancedSearchBean.getNamedTimescaleQueryType();
429
    AdvancedSearchQueryGroup qg= new AdvancedSearchQueryGroup(operator, indent);
430
    AdvancedSearchQueryGroup qgNamed, qgDates, qgDatesStart, qgDatesEnd;
431
    AdvancedSearchQueryTerm qt;
432
    String searchMode;
433
    final String namedTimescale, startDate, endDate;
434

    
435
    indent = getIndent(indentLevel * 3);
436
    namedTimescale = advancedSearchBean.getNamedTimescale();
437
    startDate = advancedSearchBean.getStartDate();
438
    endDate = advancedSearchBean.getEndDate();
439

    
440
    /* If the user specified a named timescale, check to see whether it occurs
441
     * in any of three possible places: singleDateTime, beginDate, or endDate.
442
     */
443
    qgNamed = new AdvancedSearchQueryGroup("UNION", indent);
444
    if ((namedTimescale != null) && (!(namedTimescale.equals("")))) {
445
      indent = getIndent(indentLevel * 4);
446
      searchMode = metacatSearchMode(namedTimescaleQueryType);
447
      
448
      emlField = 
449
           "temporalCoverage/singleDateTime/alternativeTimeScale/timeScaleName";
450
      qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
451
                                       namedTimescale, indent);
452
      qgNamed.addQueryTerm(qt);
453
      
454
      emlField = 
455
   "temporalCoverage/rangeOfDates/beginDate/alternativeTimeScale/timeScaleName";
456
      qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
457
                                       namedTimescale, indent);
458
      qgNamed.addQueryTerm(qt);
459
      
460
      emlField = 
461
     "temporalCoverage/rangeOfDates/endDate/alternativeTimeScale/timeScaleName";
462
      qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
463
                                       namedTimescale, indent);
464
      qgNamed.addQueryTerm(qt);
465
      
466
      addQueryGroupNamed = true;
467
    }
468
    
469
    qgDates = new AdvancedSearchQueryGroup("INTERSECT", indent);
470

    
471
    // If a start date was specified, search for temporal coverage and/or a
472
    // pubDate greater than or equal to the start date.
473
    //
474
    if ((startDate != null) && (!(startDate.equals("")))) {
475
      indent = getIndent(indentLevel * 4);
476
      qgDatesStart = new AdvancedSearchQueryGroup("UNION", indent);
477
      indent = getIndent(indentLevel * 5);
478
      searchMode = "greater-than-equals";
479

    
480
      if (dateField.equals("ALL") || dateField.equals("COLLECTION")) {
481
        emlField = "temporalCoverage/rangeOfDates/beginDate/calendarDate";
482
        qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
483
                                         startDate, indent);
484
        qgDatesStart.addQueryTerm(qt);        
485

    
486
        emlField = "temporalCoverage/singleDateTime/calendarDate";
487
        qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
488
                                         startDate, indent);
489
        qgDatesStart.addQueryTerm(qt);
490
      }
491
      
492
      if (dateField.equals("ALL") || dateField.equals("PUBLICATION")) {
493
        emlField = "dataset/pubDate";
494
        qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
495
                                         startDate, indent);
496
        qgDatesStart.addQueryTerm(qt);        
497
      }
498
      
499
      qgDates.addQueryGroup(qgDatesStart);
500
      addQueryGroupDates = true;
501
    }
502

    
503
    // If an end date was specified, search for temporal coverage and/or a
504
    // pubDate less than or equal to the end date.
505
    //
506
    if ((endDate != null) && (!(endDate.equals("")))) {
507
      indent = getIndent(indentLevel * 4);
508
      qgDatesEnd = new AdvancedSearchQueryGroup("UNION", indent);
509
      indent = getIndent(indentLevel * 5);
510
      searchMode = "less-than-equals";
511

    
512
      if (dateField.equals("ALL") || dateField.equals("COLLECTION")) {
513
        emlField = "temporalCoverage/rangeOfDates/endDate/calendarDate";
514
        qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
515
                                         endDate, indent);
516
        qgDatesEnd.addQueryTerm(qt);        
517

    
518
        emlField = "temporalCoverage/singleDateTime/calendarDate";
519
        qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
520
                                         endDate, indent);
521
        qgDatesEnd.addQueryTerm(qt);
522
      }
523
      
524
      if (dateField.equals("ALL") || dateField.equals("PUBLICATION")) {
525
        emlField = "dataset/pubDate";
526
        qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
527
                                         endDate, indent);
528
        qgDatesEnd.addQueryTerm(qt);        
529
      }      
530

    
531
      qgDates.addQueryGroup(qgDatesEnd);
532
      addQueryGroupDates = true;
533
    }
534
    
535
    if (addQueryGroupNamed) {
536
      qg.addQueryGroup(qgNamed);
537
      addQueryGroup = true;
538
    }
539
    
540
    if (addQueryGroupDates) {
541
      qg.addQueryGroup(qgDates);
542
      addQueryGroup = true;
543
    }
544
    
545
    if (addQueryGroup) {
546
      hasTemporalSearch = true;
547
      queryGroup.addQueryGroup(qg);
548
    }
549

    
550
  }
551

    
552

    
553
  /**
554
   * A taxon query searches the taxonomicClassification/taxonRankValue field,
555
   * matching the field if the user-specified value is contained in the field.
556
   */
557
  private void buildQueryTaxon() {
558
    boolean addQueryGroup = false;
559
    final String emlField;
560
    String indent = getIndent(indentLevel * 2);
561
    final String operator = "INTERSECT";
562
    AdvancedSearchQueryGroup qg= new AdvancedSearchQueryGroup(operator, indent);
563
    AdvancedSearchQueryTerm qt;
564
    final String searchMode;
565
    int taxonQueryType = advancedSearchBean.getTaxonQueryType();
566
    final String value = advancedSearchBean.getTaxon();
567
      
568
    indent = getIndent(indentLevel * 3);
569

    
570
    if ((value != null) && (!(value.equals("")))) {
571
      emlField = "taxonomicClassification/taxonRankValue";
572
      searchMode = metacatSearchMode(taxonQueryType);
573
      qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
574
                                       value, indent);
575
      qg.addQueryTerm(qt);        
576
      addQueryGroup = true;
577
    }
578

    
579
    if (addQueryGroup) {      
580
      hasTaxonomicSearch = true;
581
      queryGroup.addQueryGroup(qg);
582
    }
583
  }
584
  
585

    
586
  /**
587
   * Build a site filter. If the AdvancedSearch's site value is non-null, add a
588
   * query group that limits the results to a particular LTER site. Do this
589
   * by searching for a packageId attribute that starts with "knb-lter-xyz"
590
   * where "xyz" is the three-letter site acronym, or for a site keyword
591
   * phrase (e.g. "Kellogg Biological Station") anywhere in the documment.
592
   */
593
  private void buildSiteFilter() {
594
    String attributeValue = "";
595
    String emlField = "";
596
    String indent = getIndent(indentLevel * 2);
597
    final LTERSite lterSite = new LTERSite(site);
598
    final String operator = "UNION";
599
    AdvancedSearchQueryGroup qg= new AdvancedSearchQueryGroup(operator, indent);
600
    AdvancedSearchQueryTerm qt;
601
    String searchMode;
602
    final String siteKeyword;
603
   
604
    if (lterSite.isValidSite()) {
605
      hasSiteFilter = true;
606
      indent = getIndent(indentLevel * 3);
607
      
608
      // Handle some LTER sites with irregular naming conventions in their EML
609
      // For CAP and CWT, we need to search on the system attribute rather than
610
      // the packageId attribute
611
      //
612
      if (site.equals("CAP") || site.equals("CWT")) {
613
        emlField = "/eml/@system";
614
        attributeValue = lterSite.getSystem();
615
      }
616
      else {
617
        // For other LTER sites, search the packageId
618
        emlField = "/eml/@packageId";
619
        attributeValue = lterSite.getPackageId();
620
      }
621
      
622
      searchMode = "starts-with";
623
      qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
624
                                       attributeValue, indent);
625
      qg.addQueryTerm(qt);        
626

    
627
      // Search for site keyword phrase
628
      siteKeyword = lterSite.getSiteKeyword();
629
      emlField = "";
630
      searchMode = "contains";
631
      qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
632
                                       siteKeyword, indent);
633
      qg.addQueryTerm(qt);    
634
      
635
      queryGroup.addQueryGroup(qg); 
636
    }
637
  }
638

    
639

    
640
  /**
641
   * Counts the number of search types on the form. For example, if the user
642
   * has filled in values for both a subject search and a spatial search,
643
   * would return 2.
644
   * 
645
   * @return  searchTypes  An integer representing the total number of searches
646
   *                       that the user has filled in for this advanced search.
647
   */
648
  private int countSearchTypes () {
649
    int searchTypes = 0;
650
    
651
    if (hasSubjectSearch == true)   { searchTypes++; }
652
    if (hasAuthorSearch == true)    { searchTypes++; }
653
    if (hasSpatialSearch == true)   { searchTypes++; }
654
    if (hasTaxonomicSearch == true) { searchTypes++; }
655
    if (hasTemporalSearch == true)  { searchTypes++; }
656
    if (hasSiteFilter == true)      { searchTypes++; }
657
    
658
    return searchTypes;
659
  }
660
  
661

    
662
  /**
663
   * Builds and runs an advanced search, returning HTML result string.
664
   * 
665
   * @param metacatURL  URL to the metacat servlet
666
   * @param metacat     A metacat client object, possible null.
667
   * @param xslPath     File path to the resultset.xsl stylesheet.
668
   * @return htmlString HTML string representation of the search results.
669
   */
670
  public String executeAdvancedSearch(final String metacatURL,
671
                                      final Metacat metacat,
672
                                      final String xslPath) {
673
    String htmlString = "";
674
    int searchTypes;
675

    
676
    buildQuerySubject();
677
    buildQueryAuthor();
678
    buildQueryTaxon();
679
    buildQuerySpatialCriteria();
680
    buildQueryTemporalCriteria();
681
    buildSiteFilter();
682

    
683
    // Count the number of search types the user has entered.
684
    searchTypes = countSearchTypes();
685

    
686
    // If the user has entered values for only one type of search criteria,
687
    // then optimize the search by setting the QueryGroup object's
688
    // includeOuterQueryGroup to false. This will strip off the outer query
689
    // group and result in a more simplified SQL statement.
690
    //
691
    if (searchTypes == 1) {
692
      queryGroup.setIncludeOuterQueryGroup(false);
693
    }
694
    
695
    queryString = pathQuery.toString();
696
    htmlString = this.runQuery(metacatURL, metacat, xslPath);
697
    return htmlString;
698
  }
699
  
700

    
701
  /**
702
   * Builds and runs a simple search, returning HTML result string.
703
   * For a simple search, the AdvancedSearchBean object can be null because
704
   * all we need is a string value to search on.
705
   * 
706
   * @param metacatURL  URL to the metacat servlet
707
   * @param metacat     A metacat client object, possible null.
708
   * @param xslPath     File path to the resultset.xsl stylesheet.
709
   * @param value       String value to search on.
710
   * 
711
   * @return htmlString HTML string representation of the search results.
712
   */
713
  public String executeSearch(final String metacatURL,
714
                              final Metacat metacat,
715
                              final String xslPath,
716
                              String value) {
717
    String emlField = "";
718
    String htmlString = "";
719
    String indent = getIndent(indentLevel * 2);
720
    String operator = "UNION";
721
    AdvancedSearchQueryTerm qt;
722
    String searchMode = "contains";
723
    
724
    indent = getIndent(indentLevel * 3);
725

    
726
    /* Check whether user specified an empty search string. 
727
     */
728
    if ((value == null) || (value.equals(""))) {
729
        value = "%";
730
    }
731

    
732
    qt = new AdvancedSearchQueryTerm(searchMode, caseSensitive, emlField, 
733
                                     value, indent);
734
    queryGroup.addQueryTerm(qt);
735
    queryString = pathQuery.toString();
736
    htmlString = this.runQuery(metacatURL, metacat, xslPath);
737
    return htmlString;
738
  }
739
  
740

    
741
  /**
742
   * Returns a string of spaces that corresponds to the current indent level.
743
   * 
744
   * @param indentLevel   The number of spaces to be indented.
745
   * @return              A string containing indentLevel number of spaces.
746
   */
747
  private String getIndent(final int indentLevel) {
748
    StringBuffer indent = new StringBuffer(12);
749
    
750
    for (int i = 0; i < indentLevel; i++) {
751
      indent.append(" ");
752
    }
753
    
754
    return indent.toString();
755
  }
756
  
757

    
758
  /**
759
   * Given a AdvancedSearchBean query type, return the corresponding Metacat
760
   * searchmode string.
761
   * 
762
   * @param queryType       An int indicating the query type as specified in
763
   *                        the AdvancedSearchBean class.
764
   * @return searchmode     A string, the Metacat search mode value.
765
   */ 
766
  private String metacatSearchMode(final int queryType) {
767
    final String searchMode;
768
    
769
    switch (queryType) {
770
      case AdvancedSearchBean.CONTAINS:
771
        searchMode = "contains";
772
        break;
773
      case AdvancedSearchBean.EXACT_MATCH:
774
        searchMode = "equals";
775
        break;
776
      case AdvancedSearchBean.STARTS_WITH:
777
        searchMode = "starts-with";
778
        break;
779
      case AdvancedSearchBean.ENDS_WITH:
780
        searchMode = "ends-with";
781
        break;
782
      default:
783
        searchMode = "contains";
784
        break;
785
    }
786
    
787
    return searchMode;
788
  }
789
  
790

    
791
  /**
792
   * Parses search terms from a string. In this simple implementation, the 
793
   * string is considered to be a list of tokens separated by spaces. The more 
794
   * advanced implementation (parserTermsAdvanced) parses quoted strings 
795
   * containing spaces as a term. This method can be eliminated if we are
796
   * satisfied that parseTermsAdvanced() is working properly.
797
   * 
798
   * @param  value    The string value as entered by the user.
799
   * 
800
   * @return terms    An ArrayList of String objects. Each space-separated 
801
   *                  token is a single term.
802
   */
803
  private ArrayList parseTerms(final String value) {
804
    StringTokenizer st;
805
    ArrayList terms = new ArrayList();
806
    String token;
807
    final int tokenCount;
808
    
809
    st = new StringTokenizer(value, " ");
810
    tokenCount = st.countTokens();
811
    
812
    for (int i = 0; i < tokenCount; i++) {
813
      token = st.nextToken();
814
      terms.add(token);
815
    }
816
    
817
    return terms;
818
  }
819

    
820
  
821
  /**
822
   * Parses search terms from a string. In this advanced implementation,
823
   * double-quoted strings that contain spaces are considered a single term.
824
   * 
825
   * @param  value     The string value as entered by the user.
826
   * 
827
   * @return terms    An ArrayList of String objects. Each string is a term.
828
   */
829
  private ArrayList parseTermsAdvanced(String value) {
830
    char c;
831
    StringBuffer currentTerm = new StringBuffer(100);
832
    boolean keepSpaces = false;
833
    StringTokenizer st;
834
    final int stringLength;
835
    ArrayList terms = new ArrayList();
836
    String token;
837
    int tokenCount;
838

    
839
    value = value.trim();
840
    stringLength = value.length();
841
    
842
    for (int i = 0; i < stringLength; i++) {
843
      c = value.charAt(i);
844
  
845
      if (c == '\"') {
846
        // Termination of a quote-enclosed term. Add the current term to the
847
        // list and start a new term.
848
        if (keepSpaces) {
849
          addTerm(terms, currentTerm);
850
          currentTerm = new StringBuffer(100);
851
        }
852
      
853
        keepSpaces = !(keepSpaces); // Toggle keepSpaces to its opposite value.
854
      }
855
      else if (c == ' ') {
856
        // If we are inside a quote-enclosed term, append the space.
857
        if (keepSpaces) {
858
          currentTerm.append(c);
859
        }
860
        // Else, add the current term to the list and start a new term.
861
        else {
862
          addTerm(terms, currentTerm);
863
          currentTerm = new StringBuffer(100);
864
        }
865
      }
866
      else {
867
        // Append any non-quote, non-space characters to the current term.
868
        currentTerm.append(c);
869
      }
870
    }
871

    
872
    // Add the final term to the list.
873
    addTerm(terms, currentTerm);
874

    
875
    return terms;
876
  }
877

    
878

    
879
  /**
880
   * Runs the Metacat query for a browse search, simple search, or advanced
881
   * search.
882
   * 
883
   * @param metacatURL  URL to the metacat servlet
884
   * @param metacat     A metacat client object, possible null.
885
   * @param xslPath     File path to the resultset.xsl stylesheet.
886
   * @return htmlString HTML string representation of the search results.
887
   */
888
  private String runQuery(final String metacatURL, 
889
                          Metacat metacat, 
890
                          final String xslPath) {
891
    String htmlString = "";
892
    Reader reader;
893
    String resultset = "";
894
    String sessionId;
895
    StringReader stringReader;
896
    Stylizer stylizer = new Stylizer();
897
    
898
    if (metacat == null) {
899
      try {
900
        metacat = MetacatFactory.createMetacatConnection(metacatURL);
901
      }
902
      catch (MetacatInaccessibleException mie) {
903
        System.err.println("Metacat Inaccessible:\n" + mie.getMessage());
904
      }
905
    }
906
    
907
    sessionId = metacat.getSessionId();
908
    
909
    try {
910
      System.err.println("Starting query...");
911
      stringReader = new StringReader(queryString);
912
      reader = metacat.query(stringReader);
913
      resultset = IOUtil.getAsString(reader, true);
914
      System.err.println("Query result:\n" + resultset);
915
      htmlString = stylizer.resultsetToHTML(resultset, sessionId, 
916
                                            metacatURL, xslPath);
917
    } 
918
    catch (Exception e) {
919
      System.err.println("General exception:\n" + e.getMessage());
920
      e.printStackTrace();
921
    }
922

    
923
    return(htmlString);
924
  }
925
  
926

    
927
  /**
928
   * Main program to run a test query from the command line.
929
   * 
930
   * Pass the server name, server port, and path to resultset.xsl as the first 
931
   * three command line arguments:
932
   * 
933
   * @param argv[0]   The server name, e.g. "earth.lternet.edu"
934
   * @param argv[1]   The server port, e.g. "8080", or 0 if no port needs
935
   *                    to be specified.
936
   * @param argv[2]   The path to the resultset.xsl stylesheet, e.g.
937
   *                    "C:/Tomcat5/webapps/query/style/common/resultset.xsl"
938
   */
939
  public static void main(String[] argv) {
940
    AdvancedSearch advancedSearch;
941
    AdvancedSearchBean advancedSearchBean = new AdvancedSearchBean();
942
    String htmlString = "";
943
    MetacatHelper metacatHelper = new MetacatHelper();
944
    String metacatURL;
945
    final String serverName = argv[0];
946
    final Integer serverPortInteger = new Integer(argv[1]);
947
    final int serverPort = serverPortInteger.intValue();
948
    final String xslPath = argv[2];
949

    
950
    advancedSearchBean.setSubjectField("ALL");
951
    advancedSearchBean.setSubjectValue("bird");    
952
    //advancedSearchBean.setCreatorSurname("Walsh");
953
    //advancedSearchBean.setCreatorSurnameQueryType(0);
954
    //advancedSearchBean.setCreatorOrganization("Georgia Coastal Ecosystems");    
955
    //advancedSearchBean.setTaxon("Crustacea");    
956
    //advancedSearchBean.setLocationName("Georgia");    
957
    //advancedSearchBean.setNorthBound("31.5");    
958
    //advancedSearchBean.setSouthBound("10"); 
959
    //advancedSearchBean.setEastBound("-50");
960
    //advancedSearchBean.setWestBound("-90");
961
    //advancedSearchBean.setNamedTimescale("Phanerozoic");
962
    //advancedSearchBean.setStartDate("2001-01-01");
963
    //advancedSearchBean.setEndDate("2001-07-01");
964
    advancedSearch = new AdvancedSearch(advancedSearchBean);
965
    metacatURL = metacatHelper.constructMetacatURL(serverName, serverPort);
966
    htmlString =advancedSearch.executeAdvancedSearch(metacatURL, null, xslPath);
967
  }
968
  
969
}
(1-1/14)