| package org.apache.lucene.search; |
| |
| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import java.util.Comparator; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.TreeSet; |
| |
| import org.apache.lucene.util.ThreadInterruptedException; |
| |
| /** |
| * Filter caching singleton. It can be used to save filters locally for reuse. |
| * This class makes it possible to cache Filters even when using RMI, as it |
| * keeps the cache on the searcher side of the RMI connection. |
| * |
| * Also could be used as a persistent storage for any filter as long as the |
| * filter provides a proper hashCode(), as that is used as the key in the cache. |
| * |
| * The cache is periodically cleaned up from a separate thread to ensure the |
| * cache doesn't exceed the maximum size. |
| * |
| * @deprecated used by remote package which is deprecated as well. You should |
| * use {@link CachingWrapperFilter} if you wish to cache |
| * {@link Filter}s. |
| */ |
| @Deprecated |
| public class FilterManager { |
| |
| protected static FilterManager manager; |
| |
| /** The default maximum number of Filters in the cache */ |
| protected static final int DEFAULT_CACHE_CLEAN_SIZE = 100; |
| /** The default frequency of cache cleanup */ |
| protected static final long DEFAULT_CACHE_SLEEP_TIME = 1000 * 60 * 10; |
| |
| /** The cache itself */ |
| protected Map<Integer,FilterItem> cache; |
| /** Maximum allowed cache size */ |
| protected int cacheCleanSize; |
| /** Cache cleaning frequency */ |
| protected long cleanSleepTime; |
| /** Cache cleaner that runs in a separate thread */ |
| protected FilterCleaner filterCleaner; |
| |
| public synchronized static FilterManager getInstance() { |
| if (manager == null) { |
| manager = new FilterManager(); |
| } |
| return manager; |
| } |
| |
| /** |
| * Sets up the FilterManager singleton. |
| */ |
| protected FilterManager() { |
| cache = new HashMap<Integer,FilterItem>(); |
| cacheCleanSize = DEFAULT_CACHE_CLEAN_SIZE; // Let the cache get to 100 items |
| cleanSleepTime = DEFAULT_CACHE_SLEEP_TIME; // 10 minutes between cleanings |
| |
| filterCleaner = new FilterCleaner(); |
| Thread fcThread = new Thread(filterCleaner); |
| // set to be a Daemon so it doesn't have to be stopped |
| fcThread.setDaemon(true); |
| fcThread.start(); |
| } |
| |
| /** |
| * Sets the max size that cache should reach before it is cleaned up |
| * @param cacheCleanSize maximum allowed cache size |
| */ |
| public void setCacheSize(int cacheCleanSize) { |
| this.cacheCleanSize = cacheCleanSize; |
| } |
| |
| /** |
| * Sets the cache cleaning frequency in milliseconds. |
| * @param cleanSleepTime cleaning frequency in milliseconds |
| */ |
| public void setCleanThreadSleepTime(long cleanSleepTime) { |
| this.cleanSleepTime = cleanSleepTime; |
| } |
| |
| /** |
| * Returns the cached version of the filter. Allows the caller to pass up |
| * a small filter but this will keep a persistent version around and allow |
| * the caching filter to do its job. |
| * |
| * @param filter The input filter |
| * @return The cached version of the filter |
| */ |
| public Filter getFilter(Filter filter) { |
| synchronized(cache) { |
| FilterItem fi = null; |
| fi = cache.get(Integer.valueOf(filter.hashCode())); |
| if (fi != null) { |
| fi.timestamp = new Date().getTime(); |
| return fi.filter; |
| } |
| cache.put(Integer.valueOf(filter.hashCode()), new FilterItem(filter)); |
| return filter; |
| } |
| } |
| |
| /** |
| * Holds the filter and the last time the filter was used, to make LRU-based |
| * cache cleaning possible. |
| * TODO: Clean this up when we switch to Java 1.5 |
| */ |
| protected class FilterItem { |
| public Filter filter; |
| public long timestamp; |
| |
| public FilterItem (Filter filter) { |
| this.filter = filter; |
| this.timestamp = new Date().getTime(); |
| } |
| } |
| |
| |
| /** |
| * Keeps the cache from getting too big. |
| * If we were using Java 1.5, we could use LinkedHashMap and we would not need this thread |
| * to clean out the cache. |
| * |
| * The SortedSet sortedFilterItems is used only to sort the items from the cache, |
| * so when it's time to clean up we have the TreeSet sort the FilterItems by |
| * timestamp. |
| * |
| * Removes 1.5 * the numbers of items to make the cache smaller. |
| * For example: |
| * If cache clean size is 10, and the cache is at 15, we would remove (15 - 10) * 1.5 = 7.5 round up to 8. |
| * This way we clean the cache a bit more, and avoid having the cache cleaner having to do it frequently. |
| */ |
| protected class FilterCleaner implements Runnable { |
| |
| private boolean running = true; |
| private TreeSet<Map.Entry<Integer,FilterItem>> sortedFilterItems; |
| |
| public FilterCleaner() { |
| sortedFilterItems = new TreeSet<Map.Entry<Integer,FilterItem>>(new Comparator<Map.Entry<Integer,FilterItem>>() { |
| public int compare(Map.Entry<Integer,FilterItem> a, Map.Entry<Integer,FilterItem> b) { |
| FilterItem fia = a.getValue(); |
| FilterItem fib = b.getValue(); |
| if ( fia.timestamp == fib.timestamp ) { |
| return 0; |
| } |
| // smaller timestamp first |
| if ( fia.timestamp < fib.timestamp ) { |
| return -1; |
| } |
| // larger timestamp last |
| return 1; |
| |
| } |
| }); |
| } |
| |
| public void run () { |
| while (running) { |
| |
| // sort items from oldest to newest |
| // we delete the oldest filters |
| if (cache.size() > cacheCleanSize) { |
| // empty the temporary set |
| sortedFilterItems.clear(); |
| synchronized (cache) { |
| sortedFilterItems.addAll(cache.entrySet()); |
| Iterator<Map.Entry<Integer,FilterItem>> it = sortedFilterItems.iterator(); |
| int numToDelete = (int) ((cache.size() - cacheCleanSize) * 1.5); |
| int counter = 0; |
| // loop over the set and delete all of the cache entries not used in a while |
| while (it.hasNext() && counter++ < numToDelete) { |
| Map.Entry<Integer,FilterItem> entry = it.next(); |
| cache.remove(entry.getKey()); |
| } |
| } |
| // empty the set so we don't tie up the memory |
| sortedFilterItems.clear(); |
| } |
| // take a nap |
| try { |
| Thread.sleep(cleanSleepTime); |
| } catch (InterruptedException ie) { |
| throw new ThreadInterruptedException(ie); |
| } |
| } |
| } |
| } |
| } |