/*
* 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.backend.impl.lucene;
import java.util.Collections;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.IndexWriter;
import org.hibernate.search.cfg.spi.IdUniquenessResolver;
import org.hibernate.search.engine.service.spi.ServiceManager;
import org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity;
import org.hibernate.search.exception.impl.ErrorContextBuilder;
import org.hibernate.search.indexes.impl.PropertiesParseHelper;
import org.hibernate.search.indexes.spi.DirectoryBasedIndexManager;
import org.hibernate.search.spi.WorkerBuildContext;
import org.hibernate.search.store.Workspace;
import org.hibernate.search.store.optimization.OptimizerStrategy;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
/**
* Lucene workspace for an IndexManager
*
* @author Emmanuel Bernard
* @author Hardy Ferentschik
* @author Sanne Grinovero
*/
public abstract class AbstractWorkspaceImpl implements Workspace {
private static final Log log = LoggerFactory.make();
private final OptimizerStrategy optimizerStrategy;
private final DirectoryBasedIndexManager indexManager;
private final ServiceManager serviceManager;
protected final IndexWriterHolder writerHolder;
private final boolean deleteByTermEnforced;
private boolean indexMetadataIsComplete;
/**
* Keeps a count of modification operations done on the index.
*/
private final AtomicLong operations = new AtomicLong( 0L );
public AbstractWorkspaceImpl(DirectoryBasedIndexManager indexManager, WorkerBuildContext context, Properties cfg) {
this.indexManager = indexManager;
this.optimizerStrategy = indexManager.getOptimizerStrategy();
this.writerHolder = new IndexWriterHolder( context.getErrorHandler(), indexManager );
this.indexMetadataIsComplete = PropertiesParseHelper.isIndexMetadataComplete( cfg, context );
this.deleteByTermEnforced = context.isDeleteByTermEnforced();
this.serviceManager = context.getServiceManager();
}
@Override
public DocumentBuilderIndexedEntity getDocumentBuilder(Class<?> entity) {
return indexManager.getIndexBinding( entity ).getDocumentBuilder();
}
@Override
public Analyzer getAnalyzer(String name) {
return indexManager.getAnalyzer( name );
}
@Override
public void optimizerPhase() {
optimizerStrategy.addOperationWithinTransactionCount( operations.getAndSet( 0L ) );
optimizerStrategy.optimize( this );
}
@Override
public void performOptimization(IndexWriter writer) {
optimizerStrategy.performOptimization( writer );
}
protected void incrementModificationCounter() {
operations.addAndGet( 1 );
}
@Override
public Set<Class<?>> getEntitiesInIndexManager() {
// Do not cache it as an IndexManager receiving a new type should return an updated list
// and will trigger a LuceneBackendResources rebuild and by side effect
// a new LuceneWorkVisitor which will need the new list
return Collections.unmodifiableSet( indexManager.getContainedTypes() );
}
@Override
public void afterTransactionApplied(boolean someFailureHappened, boolean streaming) {
getCommitPolicy().onChangeSetApplied( someFailureHappened, streaming );
}
public void shutDownNow() {
getCommitPolicy().onClose();
log.shuttingDownBackend( indexManager.getIndexName() );
closeIndexWriter();
}
public void closeIndexWriter() {
log.closingIndexWriter( indexManager.getIndexName() );
writerHolder.closeIndexWriter();
}
@Override
public IndexWriter getIndexWriter() {
return writerHolder.getIndexWriter();
}
public IndexWriter getIndexWriter(ErrorContextBuilder errorContextBuilder) {
return writerHolder.getIndexWriter( errorContextBuilder );
}
@Override
public boolean areSingleTermDeletesSafe() {
return indexMetadataIsComplete && getEntitiesInIndexManager().size() == 1;
}
@Override
public boolean isDeleteByTermEnforced() {
// if artificially forced: go ahead
if ( deleteByTermEnforced ) {
return true;
}
// Optimize only if we have all the metadata
if ( indexMetadataIsComplete ) {
Set<Class<?>> entitiesInvolved = getEntitiesInIndexManager();
// a single entity is always safe
if ( entitiesInvolved.size() == 1 ) {
return true;
}
// ask the source of data for some extra info to be sure it's safe
IdUniquenessResolver idUniquenessResolver = this.serviceManager.requestService( IdUniquenessResolver.class );
try {
// Check all tuple of entities with one another with the following rules:
//
// if they use JPA ids, the range of ids is shared for both entity types
// i.e. two instances of either types are unique if and only if they have the same id value
// Note that we cannot apply this alternative following rule:
// "The id field name of one is not used as field name of the second"
// because we don't know for sure all the fields involved as FiedBridges can do a lot of things
// behind our back
// Once we have some new metadata, we can revisit
for ( Class<?> firstEntity : entitiesInvolved ) {
boolean firstEntityIsUsingJPAId =
indexManager.getIndexBinding( firstEntity )
.getDocumentBuilder()
.getTypeMetadata()
.isJpaIdUsedAsDocumentId();
boolean followingEntities = false;
for ( Class<?> secondEntity : entitiesInvolved ) {
// Skip all entities already processed and the same entity
if ( firstEntity == secondEntity ) {
followingEntities = true;
}
else if ( followingEntities ) {
//core of the validation rules
boolean secondEntityIsUsingJPAId =
indexManager.getIndexBinding( secondEntity )
.getDocumentBuilder()
.getTypeMetadata()
.isJpaIdUsedAsDocumentId();
// both use JPA id and they share the same id uniqueness set
// the boolean evaluation in important: only call areIdsUniqueForClasses if absolutely necessary
boolean uniqueIdEqualityMeansEntityEquality =
firstEntityIsUsingJPAId && secondEntityIsUsingJPAId &&
idUniquenessResolver.areIdsUniqueForClasses( firstEntity, secondEntity );
if ( !uniqueIdEqualityMeansEntityEquality ) {
return false;
}
}
}
}
}
finally {
this.serviceManager.releaseService( IdUniquenessResolver.class );
}
return true;
}
else {
return false;
}
}
@Override
public void flush() {
getCommitPolicy().onFlush();
}
@Override
public String getIndexName() {
return this.indexManager.getIndexName();
}
public IndexWriterDelegate getIndexWriterDelegate(ErrorContextBuilder errorContextBuilder) {
IndexWriter indexWriter = getIndexWriter( errorContextBuilder );
//This to respect the existing semantics of returning null on failure of IW opening
if ( indexWriter != null ) {
return new IndexWriterDelegate( indexWriter );
}
else {
return null;
}
}
public IndexWriterDelegate getIndexWriterDelegate() {
IndexWriter indexWriter = getIndexWriter();
//This to respect the existing semantics of returning null on failure of IW opening
if ( indexWriter != null ) {
return new IndexWriterDelegate( indexWriter );
}
else {
return null;
}
}
}