Package org.hibernate.search.store.impl

Source Code of org.hibernate.search.store.impl.FSSlaveDirectoryProvider$CopyDirectory

/*
* 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.store.impl;

import java.io.File;
import java.io.IOException;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.RAMDirectory;

import org.hibernate.search.engine.service.spi.ServiceManager;
import org.hibernate.search.indexes.impl.DirectoryBasedIndexManager;
import org.hibernate.search.spi.BuildContext;
import org.hibernate.search.store.DirectoryProvider;
import org.hibernate.search.util.configuration.impl.ConfigurationParseHelper;
import org.hibernate.search.util.impl.FileHelper;
import org.hibernate.search.util.logging.impl.Log;

import org.hibernate.search.exception.AssertionFailure;
import org.hibernate.search.cfg.Environment;
import org.hibernate.search.exception.SearchException;
import org.hibernate.search.util.logging.impl.LoggerFactory;

/**
* File based directory provider that takes care of getting a version of the index
* from a given source.
* The base directory is represented by hibernate.search.<index>.indexBase
* The index is created in <base directory>/<index name>
* The source (aka copy) directory is built from <sourceBase>/<index name>
* <p/>
* A copy is triggered every refresh seconds
*
* @author Emmanuel Bernard
* @author Sanne Grinovero
* @author Oliver Siegmar
*/
public class FSSlaveDirectoryProvider implements DirectoryProvider<Directory> {

  private static final Log log = LoggerFactory.make();
  private final Timer timer = new Timer( true ); //daemon thread, the copy algorithm is robust

  private volatile boolean initialized = false;
  private volatile boolean started = false;

  private volatile int current; //used also as memory barrier of all other values, which are set once.

  //variables having visibility granted by a read of "current"
  private volatile Directory dummyDirectory;
  private FSDirectory directory1;
  private FSDirectory directory2;
  private String indexName;
  private long copyChunkSize;

  //variables needed between initialize and start (used by same thread: no special care needed)
  private File sourceIndexDir;
  private File indexDir;
  private String directoryProviderName;
  private Properties properties;
  private UpdateTask updateTask;
  private ServiceManager serviceManager;

  @Override
  public void initialize(String directoryProviderName, Properties properties, BuildContext context) {
    this.properties = properties;
    this.directoryProviderName = directoryProviderName;
    this.serviceManager = context.getServiceManager();
    //source guessing
    sourceIndexDir = DirectoryProviderHelper.getSourceDirectory( directoryProviderName, properties, false );
    log.debugf( "Source directory: %s", sourceIndexDir.getPath() );
    indexDir = DirectoryProviderHelper.getVerifiedIndexDir( directoryProviderName, properties, true );
    log.debugf( "Index directory: %s", indexDir.getPath() );
    try {
      indexName = indexDir.getCanonicalPath();
    }
    catch (IOException e) {
      throw new SearchException( "Unable to initialize index: " + directoryProviderName, e );
    }
    copyChunkSize = DirectoryProviderHelper.getCopyBufferSize( directoryProviderName, properties );
    current = 0; //publish all state to other threads
  }

  private boolean currentMarkerIsInSource() {
    int retry = ConfigurationParseHelper.getIntValue( properties, Environment.RETRY_MARKER_LOOKUP, 0 );
    if ( retry < 0 ) {
      throw new SearchException( Environment.RETRY_MARKER_LOOKUP +
          " option must be a positive integer, but was \"" + retry + "\"" );
    }
    boolean currentMarkerInSource = false;
    for ( int tried = 0 ; tried <= retry ; tried++ ) {
      //we try right away the first time
      if ( tried > 0 ) {
        try {
          Thread.sleep( TimeUnit.SECONDS.toMillis( 5 ) );
        }
        catch (InterruptedException e) {
          //continue
          Thread.currentThread().interrupt();
        }
      }
      currentMarkerInSource =
          new File( sourceIndexDir, "current1" ).exists()
          || new File( sourceIndexDir, "current2" ).exists();
      if ( currentMarkerInSource ) {
        break;
      }
    }
    return currentMarkerInSource;
  }

  @Override
  public void start(DirectoryBasedIndexManager indexManager) {
    if ( ! attemptInitializeAndStart() ) {
      // if we failed to initialize and/or start, we'll try again later: setup a timer
      long period = DirectoryProviderHelper.getRetryInitializePeriod( properties, directoryProviderName );
      if ( period != 0 ) {
        scheduleTask( new InitTask(), period );
      }
      else {
        throw new SearchException( "Failed to initialize DirectoryProvider \""
            + directoryProviderName + "\": could not find marker file in index source" );
      }
    }
  }

  private void startIt() {
    @SuppressWarnings("unused")
    int readCurrentState = current; //Unneeded value, but ensure visibility of state protected by memory barrier
    int currentToBe = 0;
    try {
      directory1 = DirectoryProviderHelper.createFSIndex( new File( indexDir, "1" ), properties, serviceManager );
      directory2 = DirectoryProviderHelper.createFSIndex( new File( indexDir, "2" ), properties, serviceManager );
      File currentMarker = new File( indexDir, "current1" );
      File current2Marker = new File( indexDir, "current2" );
      if ( currentMarker.exists() ) {
        currentToBe = 1;
        if ( current2Marker.exists() ) {
          current2Marker.delete(); //TODO or throw an exception?
        }
      }
      else if ( current2Marker.exists() ) {
        currentToBe = 2;
      }
      else {
        //no default
        log.debug( "Setting directory 1 as current" );
        currentToBe = 1;
        File destinationFile = new File( indexDir, Integer.valueOf( currentToBe ).toString() );
        int sourceCurrent;
        if ( new File( sourceIndexDir, "current1" ).exists() ) {
          sourceCurrent = 1;
        }
        else if ( new File( sourceIndexDir, "current2" ).exists() ) {
          sourceCurrent = 2;
        }
        else {
          throw new SearchException( "No current file marker found in source directory: " + sourceIndexDir.getPath() );
        }
        try {
          FileHelper.synchronize(
              new File( sourceIndexDir, String.valueOf( sourceCurrent ) ),
              destinationFile, true, copyChunkSize
          );
        }
        catch (IOException e) {
          throw new SearchException( "Unable to synchronize directory: " + indexName, e );
        }
        if ( !currentMarker.createNewFile() ) {
          throw new SearchException( "Unable to create the directory marker file: " + indexName );
        }
      }
      log.debugf( "Current directory: %d", currentToBe );
    }
    catch (IOException e) {
      throw new SearchException( "Unable to initialize index: " + directoryProviderName, e );
    }
    updateTask = new UpdateTask( sourceIndexDir, indexDir );
    long period = DirectoryProviderHelper.getRefreshPeriod( properties, directoryProviderName );
    scheduleTask( updateTask, period );
    this.current = currentToBe;
    started = true;
  }

  @Override
  public Directory getDirectory() {
    if ( !started ) {
      if ( dummyDirectory == null ) {
        RAMDirectory directory = new RAMDirectory();
        DirectoryProviderHelper.initializeIndexIfNeeded( directory );
        dummyDirectory = directory;
      }
      return dummyDirectory;
    }

    int readState = current;// to have the read consistent in the next two "if"s.
    if ( readState == 1 ) {
      return directory1;
    }
    else if ( readState == 2 ) {
      return directory2;
    }
    else {
      throw new AssertionFailure( "Illegal current directory: " + readState );
    }
  }

  @Override
  public boolean equals(Object obj) {
    // this code is actually broken since the value change after initialize call
    // but from a practical POV this is fine since we only call this method
    // after initialize call
    if ( obj == this ) {
      return true;
    }
    if ( obj == null || !( obj instanceof FSSlaveDirectoryProvider ) ) {
      return false;
    }
    FSSlaveDirectoryProvider other = (FSSlaveDirectoryProvider) obj;
    //need to break memory barriers on both instances:
    @SuppressWarnings("unused")
    int readCurrentState = this.current; //unneeded value, but ensure visibility of indexName
    readCurrentState = other.current; //another unneeded value, but ensure visibility of indexName
    return indexName.equals( other.indexName );
  }

  @Override
  public int hashCode() {
    // this code is actually broken since the value change after initialize call
    // but from a practical POV this is fine since we only call this method
    // after initialize call
    @SuppressWarnings("unused")
    int readCurrentState = current; //unneeded value, but ensure visibility of indexName
    int hash = 11;
    return 37 * hash + indexName.hashCode();
  }

  class InitTask extends TimerTask {

    @Override
    public void run() {
      try {
        if ( attemptInitializeAndStart() ) {
          // then this task is no longer needed
          cancel();
        }
      }
      catch (RuntimeException re) {
        // we need this to make sure the error is logged somewhere,
        // as we're executing it in the timer thread
        log.failedSlaveDirectoryProviderInitialization( indexName, re );
      }
    }
  }

  /**
   * @return true if both initialize and start succeeded
   */
  protected synchronized boolean attemptInitializeAndStart() {
    if ( !initialized ) {
      if ( currentMarkerIsInSource() ) {
        initialized = true;
        log.foundCurrentMarker();
      }
      else {
        log.noCurrentMarkerInSourceDirectory();
      }
    }
    if ( initialized ) {
      startIt();
    }
    return this.started;
  }

  class UpdateTask extends TimerTask {

    private final ExecutorService executor;
    private final CopyDirectory copyTask;

    public UpdateTask(File sourceIndexDir, File destination) {
      executor = Executors.newSingleThreadExecutor();
      copyTask = new CopyDirectory( sourceIndexDir, destination );
    }

    @Override
    public void run() {
      if ( copyTask.inProgress.compareAndSet( false, true ) ) {
        executor.execute( copyTask );
      }
      else {
        if ( log.isTraceEnabled() ) {
          @SuppressWarnings("unused")
          int unneeded = current;//ensure visibility of indexName in Timer threads.
          log.tracef( "Skipping directory synchronization, previous work still in progress: %s", indexName );
        }
      }
    }

    public void stop() {
      executor.shutdownNow();
    }
  }

  class CopyDirectory implements Runnable {
    private final File source;
    private final File destination;
    private final AtomicBoolean inProgress = new AtomicBoolean( false );

    public CopyDirectory(File sourceIndexDir, File destination) {
      this.source = sourceIndexDir;
      this.destination = destination;
    }

    @Override
    public void run() {
      long start = System.nanoTime();
      try {
        File sourceFile = determineCurrentSourceFile();
        if ( sourceFile == null ) {
          log.unableToDetermineCurrentInSourceDirectory();
          return;
        }

        // check whether a copy is needed at all
        File currentDestinationFile = new File( destination, Integer.valueOf( current ).toString() );
        try {
          if ( FileHelper.areInSync( sourceFile, currentDestinationFile ) ) {
            if ( log.isTraceEnabled() ) {
              log.trace( "Source and destination directory are in sync. No copying required." );
            }
            return;
          }
        }
        catch (IOException ioe) {
          log.unableToCompareSourceWithDestinationDirectory( sourceFile.getName(), currentDestinationFile.getName() );
        }

        // copy is required
        int oldIndex = current;
        int index = oldIndex == 1 ? 2 : 1;
        File destinationFile = new File( destination, Integer.valueOf( index ).toString() );
        try {
          log.tracef( "Copying %s into %s", sourceFile, destinationFile );
          FileHelper.synchronize( sourceFile, destinationFile, true, copyChunkSize );
          current = index;
          log.tracef( "Copy for %s took %d ms", indexName, TimeUnit.NANOSECONDS.toMillis( System.nanoTime() - start ) );
        }
        catch (IOException e) {
          //don't change current
          log.unableToSynchronizeSource( indexName, e );
          return;
        }
        if ( !new File( indexName, "current" + oldIndex ).delete() ) {
          log.unableToRemovePreviousMarket( indexName );
        }
        try {
          new File( indexName, "current" + index ).createNewFile();
        }
        catch (IOException e) {
          log.unableToCreateCurrentMarker( indexName, e );
        }
      }
      finally {
        inProgress.set( false );
      }
    }

    /**
     * @return Return a file to the currently active source directory. Tests for the files "current1" and
     *         "current2" in order to determine which is the current directory. If there marker file does not exists
     *         <code>null</code> is returned.
     */
    private File determineCurrentSourceFile() {
      File sourceFile = null;
      if ( new File( source, "current1" ).exists() ) {
        sourceFile = new File( source, "1" );
      }
      else if ( new File( source, "current2" ).exists() ) {
        sourceFile = new File( source, "2" );
      }
      return sourceFile;
    }
  }

  @Override
  public void stop() {
    @SuppressWarnings("unused")
    int readCurrentState = current; //unneeded value, but ensure visibility of state protected by memory barrier
    timer.cancel();
    if ( updateTask != null ) {
      updateTask.stop();
    }
    closeDirectory( directory1 );
    closeDirectory( directory2 );
  }

  private void closeDirectory(Directory directory) {
    if ( directory != null ) {
      try {
        directory.close();
      }
      catch (Exception e) {
        log.unableToCloseLuceneDirectory( directory, e );
      }
    }
  }

  protected void scheduleTask(TimerTask task, long period) {
    timer.schedule( task, period, period );
  }

}
TOP

Related Classes of org.hibernate.search.store.impl.FSSlaveDirectoryProvider$CopyDirectory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.