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

« back to all changes in this revision

Viewing changes to solr/core/src/java/org/apache/solr/handler/AnalysisRequestHandlerBase.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.handler;
19
 
 
20
 
import org.apache.lucene.analysis.Analyzer;
21
 
import org.apache.lucene.analysis.CharReader;
22
 
import org.apache.lucene.analysis.CharStream;
23
 
import org.apache.lucene.analysis.TokenStream;
24
 
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
25
 
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
26
 
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
27
 
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
28
 
import org.apache.lucene.index.Payload;
29
 
import org.apache.lucene.util.Attribute;
30
 
import org.apache.lucene.util.AttributeImpl;
31
 
import org.apache.lucene.util.AttributeSource;
32
 
import org.apache.lucene.util.AttributeReflector;
33
 
import org.apache.lucene.util.ArrayUtil;
34
 
import org.apache.solr.analysis.CharFilterFactory;
35
 
import org.apache.solr.analysis.TokenFilterFactory;
36
 
import org.apache.solr.analysis.TokenizerChain;
37
 
import org.apache.solr.analysis.TokenizerFactory;
38
 
import org.apache.solr.common.util.NamedList;
39
 
import org.apache.solr.common.util.SimpleOrderedMap;
40
 
import org.apache.solr.common.SolrException;
41
 
import org.apache.solr.request.SolrQueryRequest;
42
 
import org.apache.solr.response.SolrQueryResponse;
43
 
import org.apache.solr.schema.FieldType;
44
 
 
45
 
import java.io.IOException;
46
 
import java.io.StringReader;
47
 
import java.util.*;
48
 
import java.math.BigInteger;
49
 
import org.apache.commons.lang.ArrayUtils;
50
 
 
51
 
/**
52
 
 * A base class for all analysis request handlers.
53
 
 *
54
 
 * @version $Id: AnalysisRequestHandlerBase.java 1143785 2011-07-07 11:59:59Z uschindler $
55
 
 * @since solr 1.4
56
 
 */
57
 
public abstract class AnalysisRequestHandlerBase extends RequestHandlerBase {
58
 
 
59
 
  @Override
60
 
  public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
61
 
    rsp.add("analysis", doAnalysis(req));
62
 
  }
63
 
 
64
 
  /**
65
 
   * Performs the analysis based on the given solr request and returns the analysis result as a named list.
66
 
   *
67
 
   * @param req The solr request.
68
 
   *
69
 
   * @return The analysis result as a named list.
70
 
   *
71
 
   * @throws Exception When analysis fails.
72
 
   */
73
 
  protected abstract NamedList doAnalysis(SolrQueryRequest req) throws Exception;
74
 
 
75
 
  /**
76
 
   * Analyzes the given value using the given Analyzer.
77
 
   *
78
 
   * @param value   Value to analyze
79
 
   * @param context The {@link AnalysisContext analysis context}.
80
 
   *
81
 
   * @return NamedList containing the tokens produced by analyzing the given value
82
 
   */
83
 
  protected NamedList<List<NamedList>> analyzeValue(String value, AnalysisContext context) {
84
 
 
85
 
    Analyzer analyzer = context.getAnalyzer();
86
 
 
87
 
    if (!TokenizerChain.class.isInstance(analyzer)) {
88
 
 
89
 
      TokenStream tokenStream = null;
90
 
      try {
91
 
        tokenStream = analyzer.reusableTokenStream(context.getFieldName(), new StringReader(value));
92
 
      } catch (IOException e) {
93
 
        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
94
 
      }
95
 
      NamedList<List<NamedList>> namedList = new NamedList<List<NamedList>>();
96
 
      namedList.add(tokenStream.getClass().getName(), convertTokensToNamedLists(analyzeTokenStream(tokenStream), context));
97
 
      return namedList;
98
 
    }
99
 
 
100
 
    TokenizerChain tokenizerChain = (TokenizerChain) analyzer;
101
 
    CharFilterFactory[] cfiltfacs = tokenizerChain.getCharFilterFactories();
102
 
    TokenizerFactory tfac = tokenizerChain.getTokenizerFactory();
103
 
    TokenFilterFactory[] filtfacs = tokenizerChain.getTokenFilterFactories();
104
 
 
105
 
    NamedList<List<NamedList>> namedList = new NamedList<List<NamedList>>();
106
 
 
107
 
    if( cfiltfacs != null ){
108
 
      String source = value;
109
 
      for(CharFilterFactory cfiltfac : cfiltfacs ){
110
 
        CharStream reader = CharReader.get(new StringReader(source));
111
 
        reader = cfiltfac.create(reader);
112
 
        source = writeCharStream(namedList, reader);
113
 
      }
114
 
    }
115
 
 
116
 
    TokenStream tokenStream = tfac.create(tokenizerChain.charStream(new StringReader(value)));
117
 
    List<AttributeSource> tokens = analyzeTokenStream(tokenStream);
118
 
 
119
 
    namedList.add(tokenStream.getClass().getName(), convertTokensToNamedLists(tokens, context));
120
 
 
121
 
    ListBasedTokenStream listBasedTokenStream = new ListBasedTokenStream(tokens);
122
 
 
123
 
    for (TokenFilterFactory tokenFilterFactory : filtfacs) {
124
 
      for (final AttributeSource tok : tokens) {
125
 
        tok.getAttribute(TokenTrackingAttribute.class).freezeStage();
126
 
      }
127
 
      tokenStream = tokenFilterFactory.create(listBasedTokenStream);
128
 
      tokens = analyzeTokenStream(tokenStream);
129
 
      namedList.add(tokenStream.getClass().getName(), convertTokensToNamedLists(tokens, context));
130
 
      listBasedTokenStream = new ListBasedTokenStream(tokens);
131
 
    }
132
 
 
133
 
    return namedList;
134
 
  }
135
 
 
136
 
  /**
137
 
   * Analyzes the given text using the given analyzer and returns the produced tokens.
138
 
   *
139
 
   * @param value    The value to analyze.
140
 
   * @param analyzer The analyzer to use.
141
 
   *
142
 
   * @return The produces token list.
143
 
   * @deprecated This method is no longer used by Solr
144
 
   * @see #getQueryTokenSet
145
 
   */
146
 
  @Deprecated
147
 
  protected List<AttributeSource> analyzeValue(String value, Analyzer analyzer) {
148
 
    TokenStream tokenStream = analyzer.tokenStream("", new StringReader(value));
149
 
    return analyzeTokenStream(tokenStream);
150
 
  }
151
 
 
152
 
  /**
153
 
   * Analyzes the given text using the given analyzer and returns the produced tokens.
154
 
   *
155
 
   * @param query    The query to analyze.
156
 
   * @param analyzer The analyzer to use.
157
 
   */
158
 
  protected Set<String> getQueryTokenSet(String query, Analyzer analyzer) {
159
 
    final Set<String> tokens = new HashSet<String>();
160
 
    final TokenStream tokenStream = analyzer.tokenStream("", new StringReader(query));
161
 
    final CharTermAttribute termAtt = tokenStream.addAttribute(CharTermAttribute.class);
162
 
    try {
163
 
      tokenStream.reset();
164
 
      while (tokenStream.incrementToken()) {
165
 
        tokens.add(termAtt.toString());
166
 
      }
167
 
    } catch (IOException ioe) {
168
 
      throw new RuntimeException("Error occured while iterating over tokenstream", ioe);
169
 
    }
170
 
    return tokens;
171
 
  }
172
 
 
173
 
  /**
174
 
   * Analyzes the given TokenStream, collecting the Tokens it produces.
175
 
   *
176
 
   * @param tokenStream TokenStream to analyze
177
 
   *
178
 
   * @return List of tokens produced from the TokenStream
179
 
   */
180
 
  private List<AttributeSource> analyzeTokenStream(TokenStream tokenStream) {
181
 
    final List<AttributeSource> tokens = new ArrayList<AttributeSource>();
182
 
    final PositionIncrementAttribute posIncrAtt = tokenStream.addAttribute(PositionIncrementAttribute.class);
183
 
    final TokenTrackingAttribute trackerAtt = tokenStream.addAttribute(TokenTrackingAttribute.class);
184
 
    // for backwards compatibility, add all "common" attributes
185
 
    tokenStream.addAttribute(CharTermAttribute.class);
186
 
    tokenStream.addAttribute(OffsetAttribute.class);
187
 
    tokenStream.addAttribute(TypeAttribute.class);
188
 
    try {
189
 
      tokenStream.reset();
190
 
      int position = 0;
191
 
      while (tokenStream.incrementToken()) {
192
 
        position += posIncrAtt.getPositionIncrement();
193
 
        trackerAtt.setActPosition(position);
194
 
        tokens.add(tokenStream.cloneAttributes());
195
 
      }
196
 
    } catch (IOException ioe) {
197
 
      throw new RuntimeException("Error occured while iterating over tokenstream", ioe);
198
 
    }
199
 
 
200
 
    return tokens;
201
 
  }
202
 
 
203
 
  // a static mapping of the reflected attribute keys to the names used in Solr 1.4
204
 
  static Map<String,String> ATTRIBUTE_MAPPING = Collections.unmodifiableMap(new HashMap<String,String>() {{
205
 
    put(OffsetAttribute.class.getName() + "#startOffset", "start");
206
 
    put(OffsetAttribute.class.getName() + "#endOffset", "end");
207
 
    put(TypeAttribute.class.getName() + "#type", "type");
208
 
    put(TokenTrackingAttribute.class.getName() + "#position", "position");
209
 
    put(TokenTrackingAttribute.class.getName() + "#positionHistory", "positionHistory");
210
 
  }});
211
 
 
212
 
  /**
213
 
   * Converts the list of Tokens to a list of NamedLists representing the tokens.
214
 
   *
215
 
   * @param tokens  Tokens to convert
216
 
   * @param context The analysis context
217
 
   *
218
 
   * @return List of NamedLists containing the relevant information taken from the tokens
219
 
   */
220
 
  private List<NamedList> convertTokensToNamedLists(final List<AttributeSource> tokenList, AnalysisContext context) {
221
 
    final List<NamedList> tokensNamedLists = new ArrayList<NamedList>();
222
 
    final FieldType fieldType = context.getFieldType();
223
 
    final AttributeSource[] tokens = tokenList.toArray(new AttributeSource[tokenList.size()]);
224
 
    
225
 
    // sort the tokens by absoulte position
226
 
    ArrayUtil.mergeSort(tokens, new Comparator<AttributeSource>() {
227
 
      public int compare(AttributeSource a, AttributeSource b) {
228
 
        return arrayCompare(
229
 
          a.getAttribute(TokenTrackingAttribute.class).getPositions(),
230
 
          b.getAttribute(TokenTrackingAttribute.class).getPositions()
231
 
        );
232
 
      }
233
 
      
234
 
      private int arrayCompare(int[] a, int[] b) {
235
 
        int p = 0;
236
 
        final int stop = Math.min(a.length, b.length);
237
 
        while(p < stop) {
238
 
          int diff = a[p] - b[p];
239
 
          if (diff != 0) return diff;
240
 
          p++;
241
 
        }
242
 
        // One is a prefix of the other, or, they are equal:
243
 
        return a.length - b.length;
244
 
      }
245
 
    });
246
 
 
247
 
    for (int i = 0; i < tokens.length; i++) {
248
 
      AttributeSource token = tokens[i];
249
 
      final NamedList<Object> tokenNamedList = new SimpleOrderedMap<Object>();
250
 
      final String rawText = token.addAttribute(CharTermAttribute.class).toString();
251
 
 
252
 
      String text = fieldType.indexedToReadable(rawText);
253
 
      tokenNamedList.add("text", text);
254
 
      if (!text.equals(rawText)) {
255
 
        tokenNamedList.add("raw_text", rawText);
256
 
      }
257
 
 
258
 
      if (context.getTermsToMatch().contains(rawText)) {
259
 
        tokenNamedList.add("match", true);
260
 
      }
261
 
 
262
 
      token.reflectWith(new AttributeReflector() {
263
 
        public void reflect(Class<? extends Attribute> attClass, String key, Object value) {
264
 
          // leave out position and term
265
 
          if (CharTermAttribute.class.isAssignableFrom(attClass))
266
 
            return;
267
 
          if (PositionIncrementAttribute.class.isAssignableFrom(attClass))
268
 
            return;
269
 
          
270
 
          String k = attClass.getName() + '#' + key;
271
 
          
272
 
          // map keys for "standard attributes":
273
 
          if (ATTRIBUTE_MAPPING.containsKey(k)) {
274
 
            k = ATTRIBUTE_MAPPING.get(k);
275
 
          }
276
 
          
277
 
          // TODO: special handling for payloads - move this to ResponseWriter?
278
 
          if (value instanceof Payload) {
279
 
            Payload p = (Payload) value;
280
 
            if( null != p ) {
281
 
              BigInteger bi = new BigInteger( p.getData() );
282
 
              String ret = bi.toString( 16 );
283
 
              if (ret.length() % 2 != 0) {
284
 
                // Pad with 0
285
 
                ret = "0"+ret;
286
 
              }
287
 
              value = ret;
288
 
            } else { 
289
 
              value = null;
290
 
            }
291
 
          }
292
 
 
293
 
          tokenNamedList.add(k, value);
294
 
        }
295
 
      });
296
 
 
297
 
      tokensNamedLists.add(tokenNamedList);
298
 
    }
299
 
 
300
 
    return tokensNamedLists;
301
 
  }
302
 
  
303
 
  private String writeCharStream(NamedList out, CharStream input ){
304
 
    final int BUFFER_SIZE = 1024;
305
 
    char[] buf = new char[BUFFER_SIZE];
306
 
    int len = 0;
307
 
    StringBuilder sb = new StringBuilder();
308
 
    do {
309
 
      try {
310
 
        len = input.read( buf, 0, BUFFER_SIZE );
311
 
      } catch (IOException e) {
312
 
        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
313
 
      }
314
 
      if( len > 0 )
315
 
        sb.append(buf, 0, len);
316
 
    } while( len == BUFFER_SIZE );
317
 
    out.add( input.getClass().getName(), sb.toString());
318
 
    return sb.toString();
319
 
  }
320
 
 
321
 
 
322
 
  // ================================================= Inner classes =================================================
323
 
 
324
 
  /**
325
 
   * TokenStream that iterates over a list of pre-existing Tokens
326
 
   * @lucene.internal
327
 
   */
328
 
  protected final static class ListBasedTokenStream extends TokenStream {
329
 
    private final List<AttributeSource> tokens;
330
 
    private Iterator<AttributeSource> tokenIterator;
331
 
 
332
 
    /**
333
 
     * Creates a new ListBasedTokenStream which uses the given tokens as its token source.
334
 
     *
335
 
     * @param tokens Source of tokens to be used
336
 
     */
337
 
    ListBasedTokenStream(List<AttributeSource> tokens) {
338
 
      this.tokens = tokens;
339
 
      tokenIterator = tokens.iterator();
340
 
    }
341
 
 
342
 
    @Override
343
 
    public boolean incrementToken() throws IOException {
344
 
      if (tokenIterator.hasNext()) {
345
 
        clearAttributes();
346
 
        AttributeSource next = tokenIterator.next();
347
 
        Iterator<Class<? extends Attribute>> atts = next.getAttributeClassesIterator();
348
 
        while (atts.hasNext()) // make sure all att impls in the token exist here
349
 
          addAttribute(atts.next());
350
 
        next.copyTo(this);
351
 
        return true;
352
 
      } else {
353
 
        return false;
354
 
      }
355
 
    }
356
 
 
357
 
    @Override
358
 
    public void reset() throws IOException {
359
 
      super.reset();
360
 
      tokenIterator = tokens.iterator();
361
 
    }
362
 
  }
363
 
 
364
 
  /** This is an {@link Attribute} used to track the positions of tokens
365
 
   * in the analysis chain.
366
 
   * @lucene.internal This class is only public for usage by the {@link AttributeSource} API.
367
 
   */
368
 
  public interface TokenTrackingAttribute extends Attribute {
369
 
    void freezeStage();
370
 
    void setActPosition(int pos);
371
 
    int[] getPositions();
372
 
    void reset(int[] basePositions, int position);
373
 
  }
374
 
 
375
 
  /** Implementation of {@link TokenTrackingAttribute}.
376
 
   * @lucene.internal This class is only public for usage by the {@link AttributeSource} API.
377
 
   */
378
 
  public static final class TokenTrackingAttributeImpl extends AttributeImpl implements TokenTrackingAttribute {
379
 
    private int[] basePositions = new int[0];
380
 
    private int position = 0;
381
 
    private transient int[] cachedPositions = null;
382
 
 
383
 
    public void freezeStage() {
384
 
      this.basePositions = getPositions();
385
 
      this.position = 0;
386
 
      this.cachedPositions = null;
387
 
    }
388
 
    
389
 
    public void setActPosition(int pos) {
390
 
      this.position = pos;
391
 
      this.cachedPositions = null;
392
 
    }
393
 
    
394
 
    public int[] getPositions() {
395
 
      if (cachedPositions == null) {
396
 
        cachedPositions = ArrayUtils.add(basePositions, position);
397
 
      }
398
 
      return cachedPositions;
399
 
    }
400
 
    
401
 
    public void reset(int[] basePositions, int position) {
402
 
      this.basePositions = basePositions;
403
 
      this.position = position;
404
 
      this.cachedPositions = null;
405
 
    }
406
 
    
407
 
    @Override
408
 
    public void clear() {
409
 
      // we do nothing here, as all attribute values are controlled externally by consumer
410
 
    }
411
 
    
412
 
    @Override
413
 
    public void reflectWith(AttributeReflector reflector) {
414
 
      reflector.reflect(TokenTrackingAttribute.class, "position", position);
415
 
      // convert to Integer[] array, as only such one can be serialized by ResponseWriters
416
 
      reflector.reflect(TokenTrackingAttribute.class, "positionHistory", ArrayUtils.toObject(getPositions()));
417
 
    }
418
 
 
419
 
    @Override
420
 
    public void copyTo(AttributeImpl target) {
421
 
      final TokenTrackingAttribute t = (TokenTrackingAttribute) target;
422
 
      t.reset(basePositions, position);
423
 
    }
424
 
  }
425
 
 
426
 
  /**
427
 
   * Serves as the context of an analysis process. This context contains the following constructs
428
 
   */
429
 
  protected static class AnalysisContext {
430
 
 
431
 
    private final String fieldName;
432
 
    private final FieldType fieldType;
433
 
    private final Analyzer analyzer;
434
 
    private final Set<String> termsToMatch;
435
 
 
436
 
    /**
437
 
     * Constructs a new AnalysisContext with a given field tpe, analyzer and 
438
 
     * termsToMatch. By default the field name in this context will be 
439
 
     * {@code null}. During the analysis processs, The produced tokens will 
440
 
     * be compaired to the terms in the {@code termsToMatch} set. When found, 
441
 
     * these tokens will be marked as a match.
442
 
     *
443
 
     * @param fieldType    The type of the field the analysis is performed on.
444
 
     * @param analyzer     The analyzer to be used.
445
 
     * @param termsToMatch Holds all the terms that should match during the 
446
 
     *                     analysis process.
447
 
     */
448
 
    public AnalysisContext(FieldType fieldType, Analyzer analyzer, Set<String> termsToMatch) {
449
 
      this(null, fieldType, analyzer, termsToMatch);
450
 
    }
451
 
 
452
 
    /**
453
 
     * Constructs an AnalysisContext with a given field name, field type 
454
 
     * and analyzer. By default this context will hold no terms to match
455
 
     *
456
 
     * @param fieldName The name of the field the analysis is performed on 
457
 
     *                  (may be {@code null}).
458
 
     * @param fieldType The type of the field the analysis is performed on.
459
 
     * @param analyzer  The analyzer to be used during the analysis process.
460
 
     *
461
 
     */
462
 
    public AnalysisContext(String fieldName, FieldType fieldType, Analyzer analyzer) {
463
 
      this(fieldName, fieldType, analyzer, Collections.EMPTY_SET);
464
 
    }
465
 
 
466
 
    /**
467
 
     * Constructs a new AnalysisContext with a given field tpe, analyzer and
468
 
     * termsToMatch. During the analysis processs, The produced tokens will be 
469
 
     * compaired to the termes in the {@code termsToMatch} set. When found, 
470
 
     * these tokens will be marked as a match.
471
 
     *
472
 
     * @param fieldName    The name of the field the analysis is performed on 
473
 
     *                     (may be {@code null}).
474
 
     * @param fieldType    The type of the field the analysis is performed on.
475
 
     * @param analyzer     The analyzer to be used.
476
 
     * @param termsToMatch Holds all the terms that should match during the 
477
 
     *                     analysis process.
478
 
     */
479
 
    public AnalysisContext(String fieldName, FieldType fieldType, Analyzer analyzer, Set<String> termsToMatch) {
480
 
      this.fieldName = fieldName;
481
 
      this.fieldType = fieldType;
482
 
      this.analyzer = analyzer;
483
 
      this.termsToMatch = termsToMatch;
484
 
    }
485
 
 
486
 
    public String getFieldName() {
487
 
      return fieldName;
488
 
    }
489
 
 
490
 
    public FieldType getFieldType() {
491
 
      return fieldType;
492
 
    }
493
 
 
494
 
    public Analyzer getAnalyzer() {
495
 
      return analyzer;
496
 
    }
497
 
 
498
 
    public Set<String> getTermsToMatch() {
499
 
      return termsToMatch;
500
 
    }
501
 
  }
502
 
}