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

« back to all changes in this revision

Viewing changes to lucene/contrib/facet/src/java/org/apache/lucene/facet/search/TotalFacetCountsCache.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
 
package org.apache.lucene.facet.search;
2
 
 
3
 
import java.io.File;
4
 
import java.io.IOException;
5
 
import java.util.Iterator;
6
 
import java.util.LinkedHashMap;
7
 
import java.util.concurrent.ConcurrentHashMap;
8
 
import java.util.concurrent.ConcurrentLinkedQueue;
9
 
 
10
 
import org.apache.lucene.index.IndexReader;
11
 
 
12
 
import org.apache.lucene.facet.index.params.CategoryListParams;
13
 
import org.apache.lucene.facet.index.params.FacetIndexingParams;
14
 
import org.apache.lucene.facet.search.cache.CategoryListCache;
15
 
import org.apache.lucene.facet.taxonomy.TaxonomyReader;
16
 
 
17
 
/**
18
 
 * Licensed to the Apache Software Foundation (ASF) under one or more
19
 
 * contributor license agreements.  See the NOTICE file distributed with
20
 
 * this work for additional information regarding copyright ownership.
21
 
 * The ASF licenses this file to You under the Apache License, Version 2.0
22
 
 * (the "License"); you may not use this file except in compliance with
23
 
 * the License.  You may obtain a copy of the License at
24
 
 *
25
 
 *     http://www.apache.org/licenses/LICENSE-2.0
26
 
 *
27
 
 * Unless required by applicable law or agreed to in writing, software
28
 
 * distributed under the License is distributed on an "AS IS" BASIS,
29
 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30
 
 * See the License for the specific language governing permissions and
31
 
 * limitations under the License.
32
 
 */
33
 
 
34
 
/**
35
 
 * Manage an LRU cache for {@link TotalFacetCounts} per index, taxonomy, and
36
 
 * facet indexing params.
37
 
 * 
38
 
 * @lucene.experimental
39
 
 */
40
 
public final class TotalFacetCountsCache {
41
 
  
42
 
  /**
43
 
   * Default size of in memory cache for computed total facet counts.
44
 
   * Set to 2 for the case when an application reopened a reader and 
45
 
   * the original one is still in use (Otherwise there will be 
46
 
   * switching again and again between the two.) 
47
 
   */
48
 
  public static final int DEFAULT_CACHE_SIZE = 2; 
49
 
 
50
 
  private static final TotalFacetCountsCache singleton = new TotalFacetCountsCache();
51
 
  
52
 
  /**
53
 
   * Get the single instance of this cache
54
 
   */
55
 
  public static TotalFacetCountsCache getSingleton() {
56
 
    return singleton;
57
 
  }
58
 
  
59
 
  /**
60
 
   * In-memory cache of TFCs.
61
 
   * <ul>  
62
 
   * <li>It's size is kept within limits through {@link #trimCache()}.
63
 
   * <li>An LRU eviction policy is applied, by maintaining active keys in {@link #lruKeys}. 
64
 
   * <li>After each addition to the cache, trimCache is called, to remove entries least recently used.
65
 
   * </ul>  
66
 
   * @see #markRecentlyUsed(TFCKey)
67
 
   */
68
 
  private ConcurrentHashMap<TFCKey,TotalFacetCounts> cache = new ConcurrentHashMap<TFCKey,TotalFacetCounts>();
69
 
  
70
 
  /**
71
 
   * A queue of active keys for applying LRU policy on eviction from the {@link #cache}.
72
 
   * @see #markRecentlyUsed(TFCKey)
73
 
   */
74
 
  private ConcurrentLinkedQueue<TFCKey> lruKeys = new ConcurrentLinkedQueue<TFCKey>();
75
 
  
76
 
  private int maxCacheSize = DEFAULT_CACHE_SIZE; 
77
 
  
78
 
  /** private constructor for singleton pattern */ 
79
 
  private TotalFacetCountsCache() {
80
 
  }
81
 
  
82
 
  /**
83
 
   * Get the total facet counts for a reader/taxonomy pair and facet indexing parameters.
84
 
   * If not in cache, computed here and added to the cache for later use.
85
 
   * @param indexReader the documents index
86
 
   * @param taxonomy the taxonomy index
87
 
   * @param facetIndexingParams facet indexing parameters
88
 
   * @param clCache category list cache for faster computation, can be null 
89
 
   * @return the total facet counts.
90
 
   */
91
 
  public TotalFacetCounts getTotalCounts(IndexReader indexReader, TaxonomyReader taxonomy,
92
 
      FacetIndexingParams facetIndexingParams, CategoryListCache clCache) throws IOException {
93
 
    // create the key
94
 
    TFCKey key = new TFCKey(indexReader, taxonomy, facetIndexingParams);
95
 
    // it is important that this call is not synchronized, so that available TFC 
96
 
    // would not wait for one that needs to be computed.  
97
 
    TotalFacetCounts tfc = cache.get(key);
98
 
    if (tfc != null) {
99
 
      markRecentlyUsed(key); 
100
 
      return tfc;
101
 
    }
102
 
    return computeAndCache(key, clCache);
103
 
  }
104
 
 
105
 
  /**
106
 
   * Mark key as it as recently used.
107
 
   * <p>
108
 
   * <b>Implementation notes: Synchronization considerations and the interaction between lruKeys and cache:</b>
109
 
   * <ol>
110
 
   *  <li>A concurrent {@link LinkedHashMap} would have made this class much simpler.
111
 
   *      But unfortunately, Java does not provide one.
112
 
   *      Instead, we combine two concurrent objects:
113
 
   *  <ul>
114
 
   *   <li>{@link ConcurrentHashMap} for the cached TFCs.
115
 
   *   <li>{@link ConcurrentLinkedQueue} for active keys
116
 
   *  </ul>
117
 
   *  <li>Both {@link #lruKeys} and {@link #cache} are concurrently safe.
118
 
   *  <li>Checks for a cached item through getTotalCounts() are not synchronized.
119
 
   *      Therefore, the case that a needed TFC is in the cache is very fast:
120
 
   *      it does not wait for the computation of other TFCs.
121
 
   *  <li>computeAndCache() is synchronized, and, has a (double) check of the required
122
 
   *       TFC, to avoid computing the same TFC twice. 
123
 
   *  <li>A race condition in this method (markRecentlyUsed) might result in two copies 
124
 
   *      of the same 'key' in lruKeys, but this is handled by the loop in trimCache(), 
125
 
   *      where an attempt to remove the same key twice is a no-op.
126
 
   * </ol>
127
 
   */
128
 
  private void markRecentlyUsed(TFCKey key) {
129
 
    lruKeys.remove(key);  
130
 
    lruKeys.add(key);
131
 
  }
132
 
 
133
 
  private synchronized void trimCache() {
134
 
    // loop until cache is of desired  size.
135
 
    while (cache.size()>maxCacheSize ) { 
136
 
      TFCKey key = lruKeys.poll();
137
 
      if (key==null) { //defensive
138
 
        // it is defensive since lruKeys presumably covers the cache keys 
139
 
        key = cache.keys().nextElement(); 
140
 
      }
141
 
      // remove this element. Note that an attempt to remove with the same key again is a no-op,
142
 
      // which gracefully handles the possible race in markRecentlyUsed(). 
143
 
      cache.remove(key);
144
 
    }
145
 
  }
146
 
  
147
 
  /**
148
 
   * compute TFC and cache it, after verifying it was not just added - for this
149
 
   * matter this method is synchronized, which is not too bad, because there is
150
 
   * lots of work done in the computations.
151
 
   */
152
 
  private synchronized TotalFacetCounts computeAndCache(TFCKey key, CategoryListCache clCache) throws IOException {
153
 
    TotalFacetCounts tfc = cache.get(key); 
154
 
    if (tfc == null) {
155
 
      tfc = TotalFacetCounts.compute(key.indexReader, key.taxonomy, key.facetIndexingParams, clCache);
156
 
      lruKeys.add(key);
157
 
      cache.put(key,tfc);
158
 
      trimCache();
159
 
    }
160
 
    return tfc;
161
 
  }
162
 
 
163
 
  /**
164
 
   * Load {@link TotalFacetCounts} matching input parameters from the provided outputFile 
165
 
   * and add them into the cache for the provided indexReader, taxonomy, and facetIndexingParams.
166
 
   * If a {@link TotalFacetCounts} for these parameters already exists in the cache, it will be
167
 
   * replaced by the loaded one.
168
 
   * @param inputFile file from which to read the data 
169
 
   * @param indexReader the documents index
170
 
   * @param taxonomy the taxonomy index
171
 
   * @param facetIndexingParams the facet indexing parameters
172
 
   * @throws IOException on error
173
 
   * @see #store(File, IndexReader, TaxonomyReader, FacetIndexingParams, CategoryListCache)
174
 
   */
175
 
  public synchronized void load(File inputFile, IndexReader indexReader, TaxonomyReader taxonomy,
176
 
      FacetIndexingParams facetIndexingParams) throws IOException {
177
 
    if (!inputFile.isFile() || !inputFile.exists() || !inputFile.canRead()) {
178
 
      throw new IllegalArgumentException("Exepecting an existing readable file: "+inputFile);
179
 
    }
180
 
    TFCKey key = new TFCKey(indexReader, taxonomy, facetIndexingParams);
181
 
    TotalFacetCounts tfc = TotalFacetCounts.loadFromFile(inputFile, taxonomy, facetIndexingParams);
182
 
    cache.put(key,tfc);
183
 
    trimCache();
184
 
    markRecentlyUsed(key);
185
 
  }
186
 
  
187
 
  /**
188
 
   * Store the {@link TotalFacetCounts} matching input parameters into the provided outputFile,
189
 
   * making them available for a later call to {@link #load(File, IndexReader, TaxonomyReader, FacetIndexingParams)}.
190
 
   * If these {@link TotalFacetCounts} are available in the cache, they are used. But if they are
191
 
   * not in the cache, this call will first compute them (which will also add them to the cache). 
192
 
   * @param outputFile file to store in.
193
 
   * @param indexReader the documents index
194
 
   * @param taxonomy the taxonomy index
195
 
   * @param facetIndexingParams the facet indexing parameters
196
 
   * @param clCache category list cache for faster computation, can be null
197
 
   * @throws IOException on error
198
 
   * @see #load(File, IndexReader, TaxonomyReader, FacetIndexingParams)
199
 
   * @see #getTotalCounts(IndexReader, TaxonomyReader, FacetIndexingParams, CategoryListCache)
200
 
   */
201
 
  public void store(File outputFile, IndexReader indexReader, TaxonomyReader taxonomy,
202
 
      FacetIndexingParams facetIndexingParams, CategoryListCache clCache) throws IOException {
203
 
    File parentFile = outputFile.getParentFile();
204
 
    if (
205
 
        ( outputFile.exists() && (!outputFile.isFile()      || !outputFile.canWrite())) ||
206
 
        (!outputFile.exists() && (!parentFile.isDirectory() || !parentFile.canWrite()))
207
 
      ) {
208
 
      throw new IllegalArgumentException("Exepecting a writable file: "+outputFile);
209
 
    }
210
 
    TotalFacetCounts tfc = getTotalCounts(indexReader, taxonomy, facetIndexingParams, clCache);
211
 
    TotalFacetCounts.storeToFile(outputFile, tfc);  
212
 
  }
213
 
  
214
 
  private static class TFCKey {
215
 
    final IndexReader indexReader;
216
 
    final TaxonomyReader taxonomy;
217
 
    private final Iterable<CategoryListParams> clps;
218
 
    private final int hashCode;
219
 
    private final int nDels; // needed when a reader used for faceted search was just used for deletion. 
220
 
    final FacetIndexingParams facetIndexingParams;
221
 
    
222
 
    public TFCKey(IndexReader indexReader, TaxonomyReader taxonomy,
223
 
        FacetIndexingParams facetIndexingParams) {
224
 
      this.indexReader = indexReader;
225
 
      this.taxonomy = taxonomy;
226
 
      this.facetIndexingParams = facetIndexingParams;
227
 
      this.clps = facetIndexingParams.getAllCategoryListParams();
228
 
      this.nDels = indexReader.numDeletedDocs();
229
 
      hashCode = indexReader.hashCode() ^ taxonomy.hashCode();
230
 
    }
231
 
    
232
 
    @Override
233
 
    public int hashCode() {
234
 
      return hashCode;
235
 
    }
236
 
    
237
 
    @Override
238
 
    public boolean equals(Object other) {
239
 
      TFCKey o = (TFCKey) other; 
240
 
      if (indexReader != o.indexReader || taxonomy != o.taxonomy || nDels != o.nDels) {
241
 
        return false;
242
 
      }
243
 
      Iterator<CategoryListParams> it1 = clps.iterator();
244
 
      Iterator<CategoryListParams> it2 = o.clps.iterator();
245
 
      while (it1.hasNext() && it2.hasNext()) {
246
 
        if (!it1.next().equals(it2.next())) {
247
 
          return false;
248
 
        }
249
 
      }
250
 
      return it1.hasNext() == it2.hasNext();
251
 
    }
252
 
  }
253
 
 
254
 
  /**
255
 
   * Clear the cache.
256
 
   */
257
 
  public synchronized void clear() {
258
 
    cache.clear();
259
 
    lruKeys.clear();
260
 
  }
261
 
  
262
 
  /**
263
 
   * @return the maximal cache size
264
 
   */
265
 
  public int getCacheSize() {
266
 
    return maxCacheSize;
267
 
  }
268
 
 
269
 
  /**
270
 
   * Set the number of TotalFacetCounts arrays that will remain in memory cache.
271
 
   * <p>
272
 
   * If new size is smaller than current size, the cache is appropriately trimmed.
273
 
   * <p>
274
 
   * Minimal size is 1, so passing zero or negative size would result in size of 1.
275
 
   * @param size new size to set
276
 
   */
277
 
  public void setCacheSize(int size) {
278
 
    if (size < 1) size = 1;
279
 
    int origSize = maxCacheSize;
280
 
    maxCacheSize = size;
281
 
    if (maxCacheSize < origSize) { // need to trim only if the cache was reduced
282
 
      trimCache();
283
 
    }
284
 
  }
285
 
}