~slub.team/goobi-indexserver/3.x

« back to all changes in this revision

Viewing changes to solr/core/src/java/org/apache/solr/schema/DateField.java

  • Committer: Sebastian Meyer
  • Date: 2012-08-03 09:12:40 UTC
  • Revision ID: sebastian.meyer@slub-dresden.de-20120803091240-x6861b0vabq1xror
Remove Lucene and Solr source code and add patches instead
Fix Bug #985487: Auto-suggestion for the search interface

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/**
2
 
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 
 * contributor license agreements.  See the NOTICE file distributed with
4
 
 * this work for additional information regarding copyright ownership.
5
 
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 
 * (the "License"); you may not use this file except in compliance with
7
 
 * the License.  You may obtain a copy of the License at
8
 
 *
9
 
 *     http://www.apache.org/licenses/LICENSE-2.0
10
 
 *
11
 
 * Unless required by applicable law or agreed to in writing, software
12
 
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 
 * See the License for the specific language governing permissions and
15
 
 * limitations under the License.
16
 
 */
17
 
 
18
 
package org.apache.solr.schema;
19
 
 
20
 
import org.apache.lucene.document.Fieldable;
21
 
import org.apache.lucene.index.IndexReader;
22
 
import org.apache.lucene.search.Query;
23
 
import org.apache.lucene.search.SortField;
24
 
import org.apache.lucene.search.TermRangeQuery;
25
 
import org.apache.solr.common.SolrException;
26
 
import org.apache.solr.common.util.DateUtil;
27
 
import org.apache.solr.request.SolrQueryRequest;
28
 
import org.apache.solr.response.TextResponseWriter;
29
 
import org.apache.solr.response.XMLWriter;
30
 
import org.apache.solr.search.QParser;
31
 
import org.apache.solr.search.function.*;
32
 
import org.apache.solr.util.DateMathParser;
33
 
 
34
 
import java.io.IOException;
35
 
import java.text.*;
36
 
import java.util.*;
37
 
 
38
 
// TODO: make a FlexibleDateField that can accept dates in multiple
39
 
// formats, better for human entered dates.
40
 
 
41
 
// TODO: make a DayField that only stores the day?
42
 
 
43
 
 
44
 
/**
45
 
 * FieldType that can represent any Date/Time with millisecond precision.
46
 
 * <p>
47
 
 * Date Format for the XML, incoming and outgoing:
48
 
 * </p>
49
 
 * <blockquote>
50
 
 * A date field shall be of the form 1995-12-31T23:59:59Z
51
 
 * The trailing "Z" designates UTC time and is mandatory
52
 
 * (See below for an explanation of UTC).
53
 
 * Optional fractional seconds are allowed, as long as they do not end
54
 
 * in a trailing 0 (but any precision beyond milliseconds will be ignored).
55
 
 * All other parts are mandatory.
56
 
 * </blockquote>
57
 
 * <p>
58
 
 * This format was derived to be standards compliant (ISO 8601) and is a more
59
 
 * restricted form of the
60
 
 * <a href="http://www.w3.org/TR/xmlschema-2/#dateTime-canonical-representation">canonical
61
 
 * representation of dateTime</a> from XML schema part 2.  Examples...
62
 
 * </p>
63
 
 * <ul>
64
 
 *   <li>1995-12-31T23:59:59Z</li>
65
 
 *   <li>1995-12-31T23:59:59.9Z</li>
66
 
 *   <li>1995-12-31T23:59:59.99Z</li>
67
 
 *   <li>1995-12-31T23:59:59.999Z</li>
68
 
 * </ul>
69
 
 * <p>
70
 
 * Note that DateField is lenient with regards to parsing fractional
71
 
 * seconds that end in trailing zeros and will ensure that those values
72
 
 * are indexed in the correct canonical format.
73
 
 * </p>
74
 
 * <p>
75
 
 * This FieldType also supports incoming "Date Math" strings for computing
76
 
 * values by adding/rounding internals of time relative either an explicit
77
 
 * datetime (in the format specified above) or the literal string "NOW",
78
 
 * ie: "NOW+1YEAR", "NOW/DAY", "1995-12-31T23:59:59.999Z+5MINUTES", etc...
79
 
 * -- see {@link DateMathParser} for more examples.
80
 
 * </p>
81
 
 *
82
 
 * <p>
83
 
 * Explanation of "UTC"...
84
 
 * </p>
85
 
 * <blockquote>
86
 
 * "In 1970 the Coordinated Universal Time system was devised by an
87
 
 * international advisory group of technical experts within the International
88
 
 * Telecommunication Union (ITU).  The ITU felt it was best to designate a
89
 
 * single abbreviation for use in all languages in order to minimize
90
 
 * confusion.  Since unanimous agreement could not be achieved on using
91
 
 * either the English word order, CUT, or the French word order, TUC, the
92
 
 * acronym UTC was chosen as a compromise."
93
 
 * </blockquote>
94
 
 *
95
 
 * @version $Id: DateField.java 1171741 2011-09-16 19:29:23Z hossman $
96
 
 * @see <a href="http://www.w3.org/TR/xmlschema-2/#dateTime">XML schema part 2</a>
97
 
 *
98
 
 */
99
 
public class DateField extends FieldType {
100
 
 
101
 
  public static TimeZone UTC = TimeZone.getTimeZone("UTC");
102
 
 
103
 
  /* :TODO: let Locale/TimeZone come from init args for rounding only */
104
 
 
105
 
  /** TimeZone for DateMath (UTC) */
106
 
  protected static final TimeZone MATH_TZ = UTC;
107
 
  /** Locale for DateMath (Locale.US) */
108
 
  protected static final Locale MATH_LOCALE = Locale.US;
109
 
 
110
 
  /** 
111
 
   * Fixed TimeZone (UTC) needed for parsing/formating Dates in the 
112
 
   * canonical representation.
113
 
   */
114
 
  protected static final TimeZone CANONICAL_TZ = UTC;
115
 
  /** 
116
 
   * Fixed Locale needed for parsing/formating Milliseconds in the 
117
 
   * canonical representation.
118
 
   */
119
 
  protected static final Locale CANONICAL_LOCALE = Locale.US;
120
 
  
121
 
  // The XML (external) date format will sort correctly, except if
122
 
  // fractions of seconds are present (because '.' is lower than 'Z').
123
 
  // The easiest fix is to simply remove the 'Z' for the internal
124
 
  // format.
125
 
  
126
 
  @Override
127
 
  protected void init(IndexSchema schema, Map<String,String> args) {
128
 
  }
129
 
 
130
 
  protected static String NOW = "NOW";
131
 
  protected static char Z = 'Z';
132
 
  
133
 
  @Override
134
 
  public String toInternal(String val) {
135
 
    return toInternal(parseMath(null, val));
136
 
  }
137
 
 
138
 
  /**
139
 
   * Parses a String which may be a date (in the standard format)
140
 
   * followed by an optional math expression.
141
 
   * @param now an optional fixed date to use as "NOW" in the DateMathParser
142
 
   * @param val the string to parse
143
 
   */
144
 
  public Date parseMath(Date now, String val) {
145
 
    String math = null;
146
 
    final DateMathParser p = new DateMathParser(MATH_TZ, MATH_LOCALE);
147
 
    
148
 
    if (null != now) p.setNow(now);
149
 
    
150
 
    if (val.startsWith(NOW)) {
151
 
      math = val.substring(NOW.length());
152
 
    } else {
153
 
      final int zz = val.indexOf(Z);
154
 
      if (0 < zz) {
155
 
        math = val.substring(zz+1);
156
 
        try {
157
 
          // p.setNow(toObject(val.substring(0,zz)));
158
 
          p.setNow(parseDate(val.substring(0,zz+1)));
159
 
        } catch (ParseException e) {
160
 
          throw new SolrException( SolrException.ErrorCode.BAD_REQUEST,
161
 
                                   "Invalid Date in Date Math String:'"
162
 
                                   +val+'\'',e);
163
 
        }
164
 
      } else {
165
 
        throw new SolrException( SolrException.ErrorCode.BAD_REQUEST,
166
 
                                 "Invalid Date String:'" +val+'\'');
167
 
      }
168
 
    }
169
 
 
170
 
    if (null == math || math.equals("")) {
171
 
      return p.getNow();
172
 
    }
173
 
    
174
 
    try {
175
 
      return p.parseMath(math);
176
 
    } catch (ParseException e) {
177
 
      throw new SolrException( SolrException.ErrorCode.BAD_REQUEST,
178
 
                               "Invalid Date Math String:'" +val+'\'',e);
179
 
    }
180
 
  }
181
 
  
182
 
  public String toInternal(Date val) {
183
 
    return formatDate(val);
184
 
  }
185
 
 
186
 
  @Override
187
 
  public String indexedToReadable(String indexedForm) {
188
 
    return indexedForm + Z;
189
 
  }
190
 
 
191
 
  @Override
192
 
  public String toExternal(Fieldable f) {
193
 
    return indexedToReadable(f.stringValue());
194
 
  }
195
 
 
196
 
  public Date toObject(String indexedForm) throws java.text.ParseException {
197
 
    return parseDate(indexedToReadable(indexedForm));
198
 
  }
199
 
 
200
 
  @Override
201
 
  public Date toObject(Fieldable f) {
202
 
    try {
203
 
      return parseDate( toExternal(f) );
204
 
    }
205
 
    catch( ParseException ex ) {
206
 
      throw new RuntimeException( ex );
207
 
    }
208
 
  }
209
 
 
210
 
  @Override
211
 
  public SortField getSortField(SchemaField field,boolean reverse) {
212
 
    return getStringSort(field,reverse);
213
 
  }
214
 
 
215
 
  @Override
216
 
  public void write(XMLWriter xmlWriter, String name, Fieldable f) throws IOException {
217
 
    xmlWriter.writeDate(name, toExternal(f));
218
 
  }
219
 
 
220
 
  @Override
221
 
  public void write(TextResponseWriter writer, String name, Fieldable f) throws IOException {
222
 
    writer.writeDate(name, toExternal(f));
223
 
  }
224
 
 
225
 
  /**
226
 
   * Returns a formatter that can be use by the current thread if needed to
227
 
   * convert Date objects to the Internal representation.
228
 
   *
229
 
   * Only the <tt>format(Date)</tt> can be used safely.
230
 
   * 
231
 
   * @deprecated - use formatDate(Date) instead
232
 
   */
233
 
  @Deprecated
234
 
  protected DateFormat getThreadLocalDateFormat() {
235
 
    return fmtThreadLocal.get();
236
 
  }
237
 
 
238
 
  /**
239
 
   * Thread safe method that can be used by subclasses to format a Date
240
 
   * using the Internal representation.
241
 
   */
242
 
  protected String formatDate(Date d) {
243
 
    return fmtThreadLocal.get().format(d);
244
 
  }
245
 
 
246
 
  /**
247
 
   * Return the standard human readable form of the date
248
 
   */
249
 
  public static String formatExternal(Date d) {
250
 
    return fmtThreadLocal.get().format(d) + 'Z';
251
 
  }
252
 
 
253
 
  /**
254
 
   * @see #formatExternal
255
 
   */
256
 
  public String toExternal(Date d) {
257
 
    return formatExternal(d);
258
 
  }
259
 
 
260
 
  /**
261
 
   * Thread safe method that can be used by subclasses to parse a Date
262
 
   * that is already in the internal representation
263
 
   */
264
 
   public static Date parseDate(String s) throws ParseException {
265
 
     return fmtThreadLocal.get().parse(s);
266
 
   }
267
 
 
268
 
  /** Parse a date string in the standard format, or any supported by DateUtil.parseDate */
269
 
   public Date parseDateLenient(String s, SolrQueryRequest req) throws ParseException {
270
 
     // request could define timezone in the future
271
 
     try {
272
 
       return fmtThreadLocal.get().parse(s);
273
 
     } catch (Exception e) {
274
 
       return DateUtil.parseDate(s);
275
 
     }
276
 
   }
277
 
 
278
 
  /**
279
 
   * Parses a String which may be a date
280
 
   * followed by an optional math expression.
281
 
   * @param now an optional fixed date to use as "NOW" in the DateMathParser
282
 
   * @param val the string to parse
283
 
   */
284
 
  public Date parseMathLenient(Date now, String val, SolrQueryRequest req) {
285
 
    String math = null;
286
 
    final DateMathParser p = new DateMathParser(MATH_TZ, MATH_LOCALE);
287
 
 
288
 
    if (null != now) p.setNow(now);
289
 
 
290
 
    if (val.startsWith(NOW)) {
291
 
      math = val.substring(NOW.length());
292
 
    } else {
293
 
      final int zz = val.indexOf(Z);
294
 
      if (0 < zz) {
295
 
        math = val.substring(zz+1);
296
 
        try {
297
 
          // p.setNow(toObject(val.substring(0,zz)));
298
 
          p.setNow(parseDateLenient(val.substring(0,zz+1), req));
299
 
        } catch (ParseException e) {
300
 
          throw new SolrException( SolrException.ErrorCode.BAD_REQUEST,
301
 
                                   "Invalid Date in Date Math String:'"
302
 
                                   +val+'\'',e);
303
 
        }
304
 
      } else {
305
 
        throw new SolrException( SolrException.ErrorCode.BAD_REQUEST,
306
 
                                 "Invalid Date String:'" +val+'\'');
307
 
      }
308
 
    }
309
 
 
310
 
    if (null == math || math.equals("")) {
311
 
      return p.getNow();
312
 
    }
313
 
 
314
 
    try {
315
 
      return p.parseMath(math);
316
 
    } catch (ParseException e) {
317
 
      throw new SolrException( SolrException.ErrorCode.BAD_REQUEST,
318
 
                               "Invalid Date Math String:'" +val+'\'',e);
319
 
    }
320
 
  }
321
 
 
322
 
 
323
 
  
324
 
  /**
325
 
   * Thread safe DateFormat that can <b>format</b> in the canonical
326
 
   * ISO8601 date format, not including the trailing "Z" (since it is
327
 
   * left off in the internal indexed values)
328
 
   */
329
 
  private final static ThreadLocalDateFormat fmtThreadLocal
330
 
    = new ThreadLocalDateFormat(new ISO8601CanonicalDateFormat());
331
 
  
332
 
  private static class ISO8601CanonicalDateFormat extends SimpleDateFormat {
333
 
    
334
 
    protected NumberFormat millisParser
335
 
      = NumberFormat.getIntegerInstance(CANONICAL_LOCALE);
336
 
 
337
 
    protected NumberFormat millisFormat = new DecimalFormat(".###", 
338
 
      new DecimalFormatSymbols(CANONICAL_LOCALE));
339
 
 
340
 
    public ISO8601CanonicalDateFormat() {
341
 
      super("yyyy-MM-dd'T'HH:mm:ss", CANONICAL_LOCALE);
342
 
      this.setTimeZone(CANONICAL_TZ);
343
 
    }
344
 
 
345
 
    @Override
346
 
    public Date parse(String i, ParsePosition p) {
347
 
      /* delegate to SimpleDateFormat for easy stuff */
348
 
      Date d = super.parse(i, p);
349
 
      int milliIndex = p.getIndex();
350
 
      /* worry aboutthe milliseconds ourselves */
351
 
      if (null != d &&
352
 
          -1 == p.getErrorIndex() &&
353
 
          milliIndex + 1 < i.length() &&
354
 
          '.' == i.charAt(milliIndex)) {
355
 
        p.setIndex( ++milliIndex ); // NOTE: ++ to chomp '.'
356
 
        Number millis = millisParser.parse(i, p);
357
 
        if (-1 == p.getErrorIndex()) {
358
 
          int endIndex = p.getIndex();
359
 
            d = new Date(d.getTime()
360
 
                         + (long)(millis.doubleValue() *
361
 
                                  Math.pow(10, (3-endIndex+milliIndex))));
362
 
        }
363
 
      }
364
 
      return d;
365
 
    }
366
 
 
367
 
    @Override
368
 
    public StringBuffer format(Date d, StringBuffer toAppendTo,
369
 
                               FieldPosition pos) {
370
 
      /* delegate to SimpleDateFormat for easy stuff */
371
 
      super.format(d, toAppendTo, pos);
372
 
      /* worry aboutthe milliseconds ourselves */
373
 
      long millis = d.getTime() % 1000l;
374
 
      if (0L == millis) {
375
 
        return toAppendTo;
376
 
      }
377
 
      if (millis < 0L) {
378
 
        // original date was prior to epoch
379
 
        millis += 1000L;
380
 
      }
381
 
      int posBegin = toAppendTo.length();
382
 
      toAppendTo.append(millisFormat.format(millis / 1000d));
383
 
      if (DateFormat.MILLISECOND_FIELD == pos.getField()) {
384
 
        pos.setBeginIndex(posBegin);
385
 
        pos.setEndIndex(toAppendTo.length());
386
 
      }
387
 
      return toAppendTo;
388
 
    }
389
 
 
390
 
    @Override
391
 
    public Object clone() {
392
 
      ISO8601CanonicalDateFormat c
393
 
        = (ISO8601CanonicalDateFormat) super.clone();
394
 
      c.millisParser = NumberFormat.getIntegerInstance(CANONICAL_LOCALE);
395
 
      c.millisFormat = new DecimalFormat(".###", 
396
 
        new DecimalFormatSymbols(CANONICAL_LOCALE));
397
 
      return c;
398
 
    }
399
 
  }
400
 
  
401
 
  private static class ThreadLocalDateFormat extends ThreadLocal<DateFormat> {
402
 
    DateFormat proto;
403
 
    public ThreadLocalDateFormat(DateFormat d) {
404
 
      super();
405
 
      proto = d;
406
 
    }
407
 
    @Override
408
 
    protected DateFormat initialValue() {
409
 
      return (DateFormat) proto.clone();
410
 
    }
411
 
  }
412
 
 
413
 
  @Override
414
 
  public ValueSource getValueSource(SchemaField field, QParser parser) {
415
 
    field.checkFieldCacheSource(parser);
416
 
    return new DateFieldSource(field.getName(), field.getType());
417
 
  }
418
 
 
419
 
  /** DateField specific range query */
420
 
  public Query getRangeQuery(QParser parser, SchemaField sf, Date part1, Date part2, boolean minInclusive, boolean maxInclusive) {
421
 
    return new TermRangeQuery(
422
 
            sf.getName(),
423
 
            part1 == null ? null : toInternal(part1),
424
 
            part2 == null ? null : toInternal(part2),
425
 
            minInclusive, maxInclusive);
426
 
  }
427
 
 
428
 
}
429
 
 
430
 
 
431
 
 
432
 
class DateFieldSource extends FieldCacheSource {
433
 
  // NOTE: this is bad for serialization... but we currently need the fieldType for toInternal()
434
 
  FieldType ft;
435
 
 
436
 
  public DateFieldSource(String name, FieldType ft) {
437
 
    super(name);
438
 
    this.ft = ft;
439
 
  }
440
 
 
441
 
  @Override
442
 
  public String description() {
443
 
    return "date(" + field + ')';
444
 
  }
445
 
 
446
 
  @Override
447
 
  public DocValues getValues(Map context, IndexReader reader) throws IOException {
448
 
    return new StringIndexDocValues(this, reader, field) {
449
 
      @Override
450
 
      protected String toTerm(String readableValue) {
451
 
        // needed for frange queries to work properly
452
 
        return ft.toInternal(readableValue);
453
 
      }
454
 
 
455
 
      @Override
456
 
      public float floatVal(int doc) {
457
 
        return (float)intVal(doc);
458
 
      }
459
 
 
460
 
      @Override
461
 
      public int intVal(int doc) {
462
 
        int ord=order[doc];
463
 
        return ord;
464
 
      }
465
 
 
466
 
      @Override
467
 
      public long longVal(int doc) {
468
 
        return (long)intVal(doc);
469
 
      }
470
 
 
471
 
      @Override
472
 
      public double doubleVal(int doc) {
473
 
        return (double)intVal(doc);
474
 
      }
475
 
 
476
 
      @Override
477
 
      public String strVal(int doc) {
478
 
        int ord=order[doc];
479
 
        return ft.indexedToReadable(lookup[ord]);
480
 
      }
481
 
 
482
 
      @Override
483
 
      public String toString(int doc) {
484
 
        return description() + '=' + intVal(doc);
485
 
      }
486
 
    };
487
 
  }
488
 
 
489
 
  @Override
490
 
  public boolean equals(Object o) {
491
 
    return o instanceof DateFieldSource
492
 
            && super.equals(o);
493
 
  }
494
 
 
495
 
  private static int hcode = DateFieldSource.class.hashCode();
496
 
  @Override
497
 
  public int hashCode() {
498
 
    return hcode + super.hashCode();
499
 
  };
500
 
}