/* See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* Esri Inc. 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 com.esri.gpt.catalog.lucene;
import java.io.IOException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.index.TermEnum;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.Filter;
import org.apache.lucene.util.OpenBitSet;
/**
* A filter that provides access control at a document level.
*/
public class AclFilter extends Filter {
/** instance variables ====================================================== */
private String fieldName;
private long timeMillis = 0;
private String[] userPrincipals;
/** constructors ============================================================ */
/**
* Constructs with a field name and a list of principal strings associated
* with the current user.
* @param fieldName - the ACL field name
* @param userPrincipals the user principal strings
*/
public AclFilter(String fieldName, String[] userPrincipals) {
this.fieldName = fieldName;
this.userPrincipals = userPrincipals;
}
/** properties ============================================================== */
/**
* Gets the ACL field name.
* @return the field name
*/
public String getFieldName() {
return this.fieldName;
}
/**
* Gets the execution time.
* @return the execution time (in milliseconds)
*/
public long getTimeMillis() {
return this.timeMillis;
}
/**
* Sets the execution time.
* @param millis the execution time (in milliseconds)
*/
protected void setTimeMillis(long millis) {
this.timeMillis = millis;
}
/**
* Gets the array of ACL principals associated with the active user.
* @return the array of principals
*/
public String[] getUserPrincipals() {
return this.userPrincipals;
}
/** methods ================================================================= */
/**
* Applies the filter and returns a DocIdSet of matching documents.
* @param reader the index reader
* @return the DocIdSet (documents that are visible to the supplied user principals)
* @throws IOException if an exception is encountered while reading the index
*/
public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
long t1 = System.currentTimeMillis();
OpenBitSet bitSet = this.queryValues(reader,getFieldName(),this.getUserPrincipals());
OpenBitSet publicBitSet = queryNulls(reader,getFieldName());
bitSet.or(publicBitSet);
setTimeMillis(System.currentTimeMillis() - t1);
return bitSet;
}
/**
* Queries for documents that have no values associated with the field.
* @param reader the index reader
* @return the OpenBitSet (documents with no values set to true)
* @throws IOException if an exception is encountered while reading the index
*/
private OpenBitSet queryNulls(IndexReader reader, String field) throws IOException {
int nBits = reader.maxDoc();
OpenBitSet bitSet = new OpenBitSet(nBits);
TermEnum termEnum = null;
TermDocs termDocs = null;
if ((field != null) && (field.trim().length() > 0)) {
try {
// find all documents that have a term for the field, then flip the bit set
termEnum = reader.terms(new Term(field));
termDocs = reader.termDocs();
do {
Term term = termEnum.term();
if ((term != null) && term.field().equals(field)) {
termDocs.seek(term);
while (termDocs.next()) {
bitSet.fastSet(termDocs.doc());
}
}
} while (termEnum.next());
bitSet.flip(0,nBits);
if (reader.hasDeletions()) {
for (int i=0;i<nBits;i++) {
if (bitSet.get(i) && reader.isDeleted(i)) {
bitSet.fastFlip(i);
}
}
}
} finally {
try {if (termEnum != null) termEnum.close();} catch (Exception ef) {}
try {if (termDocs != null) termDocs.close();} catch (Exception ef) {}
}
}
return bitSet;
}
/**
* Queries for documents that match one or more of the supplied values.
* @param reader the index reader
* @return the OpenBitSet (documents with matches are set to true)
* @throws IOException if an exception is encountered while reading the index
*/
private OpenBitSet queryValues(IndexReader reader, String field, String[] values) throws IOException {
OpenBitSet bitSet = new OpenBitSet(reader.maxDoc());
if ((values != null) && (values.length > 0)) {
TermDocs termDocs = null;
try {
Term baseTerm = new Term(field);
termDocs = reader.termDocs();
for (String value: values) {
termDocs.seek(baseTerm.createTerm(value.trim().toLowerCase()));
while (termDocs.next()) {
bitSet.set(termDocs.doc());
}
}
} finally {
try {if (termDocs != null) termDocs.close();} catch (Exception ef) {}
}
}
return bitSet;
}
}