| /** |
| * 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. |
| */ |
| package org.apache.blur.lucene.security.search; |
| |
| import java.io.IOException; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.concurrent.ConcurrentMap; |
| |
| import org.apache.blur.lucene.security.index.SecureAtomicReader; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.lucene.index.AtomicReader; |
| import org.apache.lucene.index.DocsEnum; |
| import org.apache.lucene.index.IndexReader; |
| import org.apache.lucene.index.IndexReader.ReaderClosedListener; |
| import org.apache.lucene.index.SegmentReader; |
| import org.apache.lucene.search.DocIdSet; |
| import org.apache.lucene.search.DocIdSetIterator; |
| import org.apache.lucene.util.Bits; |
| import org.apache.lucene.util.BytesRef; |
| import org.apache.lucene.util.OpenBitSet; |
| |
| import com.google.common.collect.MapMaker; |
| |
| public class BitSetDocumentVisibilityFilterCacheStrategy extends DocumentVisibilityFilterCacheStrategy { |
| |
| private static final Log LOG = LogFactory.getLog(BitSetDocumentVisibilityFilterCacheStrategy.class); |
| |
| public static final DocumentVisibilityFilterCacheStrategy INSTANCE = new BitSetDocumentVisibilityFilterCacheStrategy(); |
| |
| private final ConcurrentMap<Key, DocIdSet> _cache; |
| |
| public BitSetDocumentVisibilityFilterCacheStrategy() { |
| _cache = new MapMaker().makeMap(); |
| } |
| |
| @Override |
| public DocIdSet getDocIdSet(String fieldName, BytesRef term, AtomicReader reader) { |
| Key key = new Key(fieldName, term, reader.getCoreCacheKey()); |
| DocIdSet docIdSet = _cache.get(key); |
| if (docIdSet != null) { |
| LOG.debug("Cache hit for key [" + key + "]"); |
| } else { |
| LOG.debug("Cache miss for key [" + key + "]"); |
| } |
| return docIdSet; |
| } |
| |
| @Override |
| public Builder createBuilder(String fieldName, BytesRef term, final AtomicReader reader) { |
| int maxDoc = reader.maxDoc(); |
| final Key key = new Key(fieldName, term, reader.getCoreCacheKey()); |
| LOG.debug("Creating new bitset for key [" + key + "] on index [" + reader + "]"); |
| return new Builder() { |
| |
| private OpenBitSet bitSet = new OpenBitSet(maxDoc); |
| |
| @Override |
| public void or(DocIdSetIterator it) throws IOException { |
| LOG.debug("Building bitset for key [" + key + "]"); |
| int doc; |
| while ((doc = it.nextDoc()) != DocsEnum.NO_MORE_DOCS) { |
| bitSet.set(doc); |
| } |
| } |
| |
| @Override |
| public DocIdSet getDocIdSet() throws IOException { |
| SegmentReader segmentReader = getSegmentReader(reader); |
| segmentReader.addReaderClosedListener(new ReaderClosedListener() { |
| @Override |
| public void onClose(IndexReader reader) { |
| LOG.debug("Removing old bitset for key [" + key + "]"); |
| DocIdSet docIdSet = _cache.remove(key); |
| if (docIdSet == null) { |
| LOG.warn("DocIdSet was missing for key [" + docIdSet + "]"); |
| } |
| } |
| }); |
| long cardinality = bitSet.cardinality(); |
| DocIdSet cacheDocIdSet; |
| if (isFullySet(maxDoc, bitSet, cardinality)) { |
| cacheDocIdSet = getFullySetDocIdSet(maxDoc); |
| } else if (isFullyEmpty(bitSet, cardinality)) { |
| cacheDocIdSet = getFullyEmptyDocIdSet(maxDoc); |
| } else { |
| cacheDocIdSet = bitSet; |
| } |
| _cache.put(key, cacheDocIdSet); |
| return cacheDocIdSet; |
| } |
| }; |
| } |
| |
| public static DocIdSet getFullyEmptyDocIdSet(int maxDoc) { |
| Bits bits = getFullyEmptyBits(maxDoc); |
| return new DocIdSet() { |
| @Override |
| public DocIdSetIterator iterator() throws IOException { |
| return getFullyEmptyDocIdSetIterator(maxDoc); |
| } |
| |
| @Override |
| public Bits bits() throws IOException { |
| return bits; |
| } |
| |
| @Override |
| public boolean isCacheable() { |
| return true; |
| } |
| }; |
| } |
| |
| public static DocIdSetIterator getFullyEmptyDocIdSetIterator(int maxDoc) { |
| return new DocIdSetIterator() { |
| |
| private int _docId = -1; |
| |
| @Override |
| public int docID() { |
| return _docId; |
| } |
| |
| @Override |
| public int nextDoc() throws IOException { |
| return _docId = DocIdSetIterator.NO_MORE_DOCS; |
| } |
| |
| @Override |
| public int advance(int target) throws IOException { |
| return _docId = DocIdSetIterator.NO_MORE_DOCS; |
| } |
| |
| @Override |
| public long cost() { |
| return 0; |
| } |
| }; |
| } |
| |
| public static Bits getFullyEmptyBits(int maxDoc) { |
| return new Bits() { |
| @Override |
| public boolean get(int index) { |
| return false; |
| } |
| |
| @Override |
| public int length() { |
| return maxDoc; |
| } |
| }; |
| } |
| |
| public static DocIdSet getFullySetDocIdSet(int maxDoc) { |
| Bits bits = getFullySetBits(maxDoc); |
| return new DocIdSet() { |
| @Override |
| public DocIdSetIterator iterator() throws IOException { |
| return getFullySetDocIdSetIterator(maxDoc); |
| } |
| |
| @Override |
| public Bits bits() throws IOException { |
| return bits; |
| } |
| |
| @Override |
| public boolean isCacheable() { |
| return true; |
| } |
| }; |
| } |
| |
| public static DocIdSetIterator getFullySetDocIdSetIterator(int maxDoc) { |
| return new DocIdSetIterator() { |
| |
| private int _docId = -1; |
| |
| @Override |
| public int advance(int target) throws IOException { |
| if (_docId == DocIdSetIterator.NO_MORE_DOCS) { |
| return DocIdSetIterator.NO_MORE_DOCS; |
| } |
| _docId = target; |
| if (_docId >= maxDoc) { |
| return _docId = DocIdSetIterator.NO_MORE_DOCS; |
| } |
| return _docId; |
| } |
| |
| @Override |
| public int nextDoc() throws IOException { |
| if (_docId == DocIdSetIterator.NO_MORE_DOCS) { |
| return DocIdSetIterator.NO_MORE_DOCS; |
| } |
| _docId++; |
| if (_docId >= maxDoc) { |
| return _docId = DocIdSetIterator.NO_MORE_DOCS; |
| } |
| return _docId; |
| } |
| |
| @Override |
| public int docID() { |
| return _docId; |
| } |
| |
| @Override |
| public long cost() { |
| return 0l; |
| } |
| |
| }; |
| } |
| |
| public static Bits getFullySetBits(int maxDoc) { |
| return new Bits() { |
| @Override |
| public boolean get(int index) { |
| return true; |
| } |
| |
| @Override |
| public int length() { |
| return maxDoc; |
| } |
| }; |
| } |
| |
| public static boolean isFullyEmpty(OpenBitSet bitSet, long cardinality) { |
| if (cardinality == 0) { |
| return true; |
| } |
| return false; |
| } |
| |
| public static boolean isFullySet(int maxDoc, OpenBitSet bitSet, long cardinality) { |
| if (cardinality >= maxDoc) { |
| return true; |
| } |
| return false; |
| } |
| |
| public static SegmentReader getSegmentReader(IndexReader indexReader) throws IOException { |
| if (indexReader instanceof SegmentReader) { |
| return (SegmentReader) indexReader; |
| } else if (indexReader instanceof SecureAtomicReader) { |
| SecureAtomicReader atomicReader = (SecureAtomicReader) indexReader; |
| AtomicReader originalReader = atomicReader.getOriginalReader(); |
| return getSegmentReader(originalReader); |
| } else { |
| try { |
| Method method = indexReader.getClass().getDeclaredMethod("getOriginalReader", new Class[] {}); |
| return getSegmentReader((IndexReader) method.invoke(indexReader, new Object[] {})); |
| } catch (NoSuchMethodException e) { |
| LOG.error("IndexReader cannot find method [getOriginalReader]"); |
| } catch (SecurityException e) { |
| throw new IOException(e); |
| } catch (IllegalAccessException e) { |
| throw new IOException(e); |
| } catch (IllegalArgumentException e) { |
| throw new IOException(e); |
| } catch (InvocationTargetException e) { |
| throw new IOException(e); |
| } |
| } |
| throw new IOException("SegmentReader could not be found [" + indexReader + "]."); |
| } |
| |
| private static class Key { |
| |
| private final Object _object; |
| private final BytesRef _term; |
| private final String _fieldName; |
| |
| public Key(String fieldName, BytesRef term, Object object) { |
| _fieldName = fieldName; |
| _term = BytesRef.deepCopyOf(term); |
| _object = object; |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + ((_fieldName == null) ? 0 : _fieldName.hashCode()); |
| result = prime * result + ((_object == null) ? 0 : _object.hashCode()); |
| result = prime * result + ((_term == null) ? 0 : _term.hashCode()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) |
| return true; |
| if (obj == null) |
| return false; |
| if (getClass() != obj.getClass()) |
| return false; |
| Key other = (Key) obj; |
| if (_fieldName == null) { |
| if (other._fieldName != null) |
| return false; |
| } else if (!_fieldName.equals(other._fieldName)) |
| return false; |
| if (_object == null) { |
| if (other._object != null) |
| return false; |
| } else if (!_object.equals(other._object)) |
| return false; |
| if (_term == null) { |
| if (other._term != null) |
| return false; |
| } else if (!_term.equals(other._term)) |
| return false; |
| return true; |
| } |
| |
| @Override |
| public String toString() { |
| return "Key [_object=" + _object + ", _fieldName=" + _fieldName + ", _term=" + _term + "]"; |
| } |
| |
| } |
| |
| @Override |
| public String toString() { |
| return "BitSetDocumentVisibilityFilterCacheStrategy [_cache=" + _cache + "]"; |
| } |
| |
| } |