/** * 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 + "]"; } }