/* * Hibernate Search, full-text search for your domain model * * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.search.query.engine.impl; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Set; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.FieldType; import org.apache.lucene.document.StoredField; import org.apache.lucene.document.TextField; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.StoredFieldVisitor; /** * Inspired by Lucene's DocumentStoredFieldVisitor, with the difference that we want * to reuse the same FieldVisitor to load multiple Document instances. * Also the accepted fields are kept in a linked-list like structure, to optimize * for small amounts of accepted fields. * * A ReusableDocumentStoredFieldVisitor is NOT threadsafe: in case you need one * for multiple threads make a clone for each thread. * * @see org.apache.lucene.document.DocumentStoredFieldVisitor * @author Sanne Grinovero */ public final class ReusableDocumentStoredFieldVisitor extends StoredFieldVisitor { private static final FieldAcceptor NOT_ACCEPT = new DenyingFieldAcceptor(); private final FieldAcceptor rootAcceptor; private final int totalFields; //The Lucene Document which will be returned. Lazily initialized. private Document doc = null; //Counts the number of fields which have not been loaded yet (counting down from the known set of needed fields) //This field needs to be reset to the value of totalFields when the doc field is changed. private int missingFields; public ReusableDocumentStoredFieldVisitor(Set<String> fieldsToLoad) { FieldAcceptor previous = NOT_ACCEPT; for ( String fieldName : fieldsToLoad ) { previous = new ChainedFieldAcceptor( previous, fieldName ); } this.rootAcceptor = previous; this.totalFields = fieldsToLoad.size(); this.missingFields = totalFields; } @Override public void binaryField(FieldInfo fieldInfo, byte[] value) throws IOException { getDocument().add( new StoredField( fieldInfo.name, value ) ); } @Override public void stringField(FieldInfo fieldInfo, byte[] value) throws IOException { final FieldType ft = new FieldType( TextField.TYPE_STORED ); ft.setStoreTermVectors( fieldInfo.hasVectors() ); ft.setOmitNorms( fieldInfo.omitsNorms() ); ft.setIndexOptions( fieldInfo.getIndexOptions() ); getDocument().add( new Field( fieldInfo.name, new String( value, StandardCharsets.UTF_8 ), ft ) ); } @Override public void intField(FieldInfo fieldInfo, int value) { getDocument().add( new StoredField( fieldInfo.name, value ) ); } @Override public void longField(FieldInfo fieldInfo, long value) { getDocument().add( new StoredField( fieldInfo.name, value ) ); } @Override public void floatField(FieldInfo fieldInfo, float value) { getDocument().add( new StoredField( fieldInfo.name, value ) ); } @Override public void doubleField(FieldInfo fieldInfo, double value) { getDocument().add( new StoredField( fieldInfo.name, value ) ); } @Override public Status needsField(FieldInfo fieldInfo) throws IOException { if ( missingFields == 0 ) { // An aggressive STOP could prevent unnecessary I/O ! return Status.STOP; } final Status s = rootAcceptor.acceptField( fieldInfo.name ); if ( s == Status.YES ) { missingFields--; } return s; } /** * Useful for tests * @return the amount of accepted fields */ public int countAcceptedFields() { FieldAcceptor acceptor = rootAcceptor; int count = 0; while ( acceptor != NOT_ACCEPT ) { //If it's not negative it has to be positive: ChainedFieldAcceptor positiveAcceptor = (ChainedFieldAcceptor) acceptor; acceptor = positiveAcceptor.next; count++; } return count; } /** * Retrieve the visited document, and resets the instance to be reused by creating a new Document * internally. * * @return Document populated with stored fields. */ public Document getDocumentAndReset() { final Document localDoc = this.doc; if ( localDoc == null ) { return new Document(); } else { this.doc = null; this.missingFields = totalFields; return localDoc; } } private Document getDocument() { Document localDoc = this.doc; if ( localDoc == null ) { localDoc = new Document(); this.doc = localDoc; } return localDoc; } /* The structure below shapes a chain of accepted field names: * you could think of it as a linked list. */ private interface FieldAcceptor { Status acceptField(String fieldName); } private static final class DenyingFieldAcceptor implements FieldAcceptor { @Override public Status acceptField(final String fieldName) { return Status.NO; } } private static final class ChainedFieldAcceptor implements FieldAcceptor { final FieldAcceptor next; final String acceptedFieldName; ChainedFieldAcceptor(FieldAcceptor next, String acceptedFieldName) { this.next = next; this.acceptedFieldName = acceptedFieldName; } @Override public Status acceptField(final String fieldName) { if ( acceptedFieldName.equals( fieldName ) ) { return Status.YES; } else { return next.acceptField( fieldName ); } } } }