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: 2006-06-14 08:35:26 -0700 (Wed, 14 Jun 2006) $'
9
 * '$Revision: 3008 $'
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

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

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

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

    
62

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

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

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

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

    
116

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

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

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

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

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

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

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

    
198
  }
199
  
200

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
548
  }
549

    
550

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

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

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

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

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

    
637

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

    
660
  /**
661
   * Builds and runs an advanced search, returning HTML result string.
662
   * 
663
   * @param metacatURL  URL to the metacat servlet
664
   * @param metacat     A metacat client object, possible null.
665
   * @param qformat     The qformat (skin) to use when displaying results.
666
   * @param xslPath     File path to the resultset.xsl stylesheet.
667
   * @return htmlString HTML string representation of the search results.
668
   */
669
  public String executeAdvancedSearch(final String metacatURL,
670
                                      final Metacat metacat,
671
                                      final String qformat,
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, qformat, 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 qformat     The qformat (skin) to use when displaying results.
709
   * @param xslPath     File path to the resultset.xsl stylesheet.
710
   * @param value       String value to search on.
711
   * 
712
   * @return htmlString HTML string representation of the search results.
713
   */
714
  public String executeSearch(final String metacatURL,
715
                              final Metacat metacat,
716
                              final String qformat,
717
                              final String xslPath,
718
                              String value) {
719
    String emlField = "";
720
    String htmlString = "";
721
    String indent = getIndent(indentLevel * 2);
722
    AdvancedSearchQueryTerm qt;
723
    String searchMode = "contains";
724
    
725
    indent = getIndent(indentLevel * 3);
726

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

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

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

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

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

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

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

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

    
874
    return terms;
875
  }
876

    
877

    
878
  /**
879
   * Runs the Metacat query for a browse search, simple search, or advanced
880
   * search.
881
   * 
882
   * @param metacatURL  URL to the metacat servlet
883
   * @param metacat     A metacat client object, possible null.
884
   * @param qformat     The qformat (skin) to use when displaying results.
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 qformat,
891
                          final String xslPath) {
892
    String htmlString = "";
893
    Reader reader;
894
    String resultset = "";
895
    String sessionId;
896
    StringReader stringReader;
897
    Stylizer stylizer = new Stylizer();
898
    
899
    if (metacat == null) {
900
      try {
901
        metacat = MetacatFactory.createMetacatConnection(metacatURL);
902
      }
903
      catch (MetacatInaccessibleException mie) {
904
        System.err.println("Metacat Inaccessible:\n" + mie.getMessage());
905
      }
906
    }
907
    
908
    sessionId = metacat.getSessionId();
909
    
910
    try {
911
      System.err.println("Starting query...");
912
      stringReader = new StringReader(queryString);
913
      reader = metacat.query(stringReader);
914
      resultset = IOUtil.getAsString(reader, true);
915
      System.err.println("Query result:\n" + resultset);
916
      htmlString = stylizer.resultsetToHTML(resultset, sessionId, 
917
                                            metacatURL, qformat, xslPath);
918
    } 
919
    catch (Exception e) {
920
      System.err.println("General exception:\n" + e.getMessage());
921
      e.printStackTrace();
922
    }
923

    
924
    return(htmlString);
925
  }
926
  
927

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

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