/* * Copyright 2000-2013 Enonic AS * http://www.enonic.com/license */ package com.enonic.cms.core.search; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import com.enonic.cms.core.content.ContentEntity; import com.enonic.cms.core.content.ContentKey; import com.enonic.cms.core.content.ContentMap; import com.enonic.cms.core.content.IndexService; import com.enonic.cms.core.search.query.ContentDocument; import com.enonic.cms.core.search.query.ContentIndexService; import com.enonic.cms.store.dao.ContentDao; import com.enonic.cms.store.dao.ContentEagerFetches; import com.enonic.cms.store.dao.FindContentByKeysCommand; import static com.enonic.cms.core.search.IndexTransactionJournalEntry.JournalOperation.UPDATE; public class IndexTransactionJournal implements TransactionSynchronization { private final static Logger LOG = LoggerFactory.getLogger( IndexTransactionJournal.class ); private final ContentDao contentDao; private final IndexService indexService; private final ContentIndexService contentIndexService; private final Set<IndexTransactionJournalEntry> changeHistory; public IndexTransactionJournal( ContentIndexService contentIndexService, IndexService indexService, ContentDao contentDao ) { this.contentIndexService = contentIndexService; this.indexService = indexService; this.contentDao = contentDao; this.changeHistory = new HashSet<IndexTransactionJournalEntry>(); } public void startTransaction() { registerSynchronization(); LOG.debug( "Index transaction started" ); } private void registerSynchronization() { if ( !TransactionSynchronizationManager.getSynchronizations().contains( this ) ) { TransactionSynchronizationManager.registerSynchronization( this ); } } public void registerUpdate( Collection<ContentKey> contentKeys, boolean updateMetadataOnly ) { if ( TransactionSynchronizationManager.isCurrentTransactionReadOnly() ) { return; } for ( ContentKey contentKey : contentKeys ) { final IndexTransactionJournalEntry indexTransactionJournalEntry = new IndexTransactionJournalEntry( UPDATE, contentKey, updateMetadataOnly ); changeHistory.add( indexTransactionJournalEntry ); } } public void registerUpdate( ContentKey contentKey, boolean updateMetadataOnly ) { if ( TransactionSynchronizationManager.isCurrentTransactionReadOnly() ) { return; } final IndexTransactionJournalEntry indexTransactionJournalEntry = new IndexTransactionJournalEntry( UPDATE, contentKey, updateMetadataOnly ); changeHistory.add( indexTransactionJournalEntry ); } public void registerRemove( ContentKey contentKey ) { changeHistory.add( new IndexTransactionJournalEntry( IndexTransactionJournalEntry.JournalOperation.DELETE, contentKey ) ); } private void flushIndexChanges() { if ( changeHistory.isEmpty() ) { LOG.debug( "No changes found in transaction, skipping index update." ); return; } if ( TransactionSynchronizationManager.isCurrentTransactionReadOnly() ) { LOG.debug( "Read-only transaction, nothing to do." ); return; } final ContentMap contentMap = preloadContent(); LOG.debug( "Flushing index changes from transaction journal" ); for ( IndexTransactionJournalEntry journalEntry : changeHistory ) { switch ( journalEntry.getOperation() ) { case UPDATE: handleFlushUpdateOperation( journalEntry, contentMap ); break; case DELETE: handleFlushDeleteOperation( journalEntry ); break; } } changeHistory.clear(); flushIndex(); } private ContentMap preloadContent() { List<ContentKey> contentToLoad = new ArrayList<ContentKey>(); for ( IndexTransactionJournalEntry journalEntry : changeHistory ) { if ( journalEntry.getOperation() == UPDATE ) { contentToLoad.add( journalEntry.getContentKey() ); } } FindContentByKeysCommand command = new FindContentByKeysCommand().contentKeys( contentToLoad ).eagerFetches( ContentEagerFetches.PRESET_FOR_INDEXING ).fetchEntitiesAsReadOnly( false ).byPassCache( false ); return contentDao.findByKeys( command ); } private void handleFlushUpdateOperation( final IndexTransactionJournalEntry journalEntry, final ContentMap contentMap ) { final ContentEntity content = contentMap.get( journalEntry.getContentKey() ); if ( content == null ) { LOG.warn( "Content to update index for did not exist (removing index for content instead): " + journalEntry.getContentKey() ); deleteContent( journalEntry.getContentKey() ); } else if ( content.isDeleted() ) { deleteContent( content.getKey() ); } else { doUpdateContent( content, journalEntry.isUpdateMetadataOnly() ); } } private void handleFlushDeleteOperation( final IndexTransactionJournalEntry journalEntry ) { deleteContent( journalEntry.getContentKey() ); } private void doUpdateContent( final ContentEntity content, final boolean updateMetadataOnly ) { final ContentDocument doc = indexService.createContentDocument( content, updateMetadataOnly ); LOG.debug( "Updating index for content: " + doc.getContentKey().toString() ); contentIndexService.index( doc, updateMetadataOnly ); } private void deleteContent( ContentKey contentKey ) { LOG.debug( "Deleting index for content: " + contentKey.toString() ); contentIndexService.remove( contentKey ); } private void flushIndex() { contentIndexService.flush(); } @Override public void afterCommit() { TransactionSynchronizationManager.unbindResourceIfPossible( IndexTransactionServiceImpl.TRANSACTION_JOURNAL_KEY ); flushIndexChanges(); } @Override public void suspend() { } @Override public void resume() { } @Override public void flush() { } @Override public void beforeCommit( boolean b ) { } @Override public void beforeCompletion() { } @Override public void afterCompletion( int i ) { // System.out.println( "Completion status: " + i ); } public void clearJournal() { TransactionSynchronizationManager.unbindResourceIfPossible( IndexTransactionServiceImpl.TRANSACTION_JOURNAL_KEY ); changeHistory.clear(); } }