/** @author John Griffin */
public class FieldBoostTest extends SearchTestCase {

  private static final Logger log = LoggerFactory.make();

  public void testBoostedGetDesc() throws Exception {
    FullTextSession fullTextSession = Search.getFullTextSession(openSession());
    buildBoostedGetIndex(fullTextSession);

    fullTextSession.clear();
    Transaction tx = fullTextSession.beginTransaction();

    QueryParser authorParser =
        new QueryParser(getTargetLuceneVersion(), "author", SearchTestCase.standardAnalyzer);
    QueryParser descParser =
        new QueryParser(getTargetLuceneVersion(), "description", SearchTestCase.standardAnalyzer);
    Query author = authorParser.parse("Wells");
    Query desc = descParser.parse("martians");

    BooleanQuery query = new BooleanQuery();
    query.add(author, BooleanClause.Occur.SHOULD);
    query.add(desc, BooleanClause.Occur.SHOULD);
    log.debug(query.toString());

    org.hibernate.search.FullTextQuery hibQuery =
        fullTextSession.createFullTextQuery(query, BoostedGetDescriptionLibrary.class);
    List results = hibQuery.list();

    log.debug(hibQuery.explain(0).toString());
    log.debug(hibQuery.explain(1).toString());

    assertTrue(
        "incorrect document returned",
        ((BoostedGetDescriptionLibrary) results.get(0)).getDescription().startsWith("Martians"));

    // cleanup
    for (Object element :
        fullTextSession
            .createQuery("from " + BoostedGetDescriptionLibrary.class.getName())
            .list()) {
      fullTextSession.delete(element);
    }
    tx.commit();
    fullTextSession.close();
  }

  public void testBoostedFieldDesc() throws Exception {
    FullTextSession fullTextSession = Search.getFullTextSession(openSession());
    buildBoostedFieldIndex(fullTextSession);

    fullTextSession.clear();
    Transaction tx = fullTextSession.beginTransaction();

    QueryParser authorParser =
        new QueryParser(getTargetLuceneVersion(), "author", SearchTestCase.standardAnalyzer);
    QueryParser descParser =
        new QueryParser(getTargetLuceneVersion(), "description", SearchTestCase.standardAnalyzer);
    Query author = authorParser.parse("Wells");
    Query desc = descParser.parse("martians");

    BooleanQuery query = new BooleanQuery();
    query.add(author, BooleanClause.Occur.SHOULD);
    query.add(desc, BooleanClause.Occur.SHOULD);
    log.debug(query.toString());

    org.hibernate.search.FullTextQuery hibQuery =
        fullTextSession.createFullTextQuery(query, BoostedFieldDescriptionLibrary.class);
    List results = hibQuery.list();

    assertTrue(
        "incorrect document boost",
        ((BoostedFieldDescriptionLibrary) results.get(0)).getDescription().startsWith("Martians"));

    log.debug(hibQuery.explain(0).toString());
    log.debug(hibQuery.explain(1).toString());

    // cleanup
    for (Object element :
        fullTextSession
            .createQuery("from " + BoostedFieldDescriptionLibrary.class.getName())
            .list()) {
      fullTextSession.delete(element);
    }
    tx.commit();
    fullTextSession.close();
  }

  public void testBoostedDesc() throws Exception {
    FullTextSession fullTextSession = Search.getFullTextSession(openSession());
    buildBoostedDescIndex(fullTextSession);

    fullTextSession.clear();
    Transaction tx = fullTextSession.beginTransaction();

    QueryParser authorParser =
        new QueryParser(getTargetLuceneVersion(), "author", SearchTestCase.standardAnalyzer);
    QueryParser descParser =
        new QueryParser(getTargetLuceneVersion(), "description", SearchTestCase.standardAnalyzer);
    Query author = authorParser.parse("Wells");
    Query desc = descParser.parse("martians");

    BooleanQuery query = new BooleanQuery();
    query.add(author, BooleanClause.Occur.SHOULD);
    query.add(desc, BooleanClause.Occur.SHOULD);
    log.debug(query.toString());

    org.hibernate.search.FullTextQuery hibQuery =
        fullTextSession.createFullTextQuery(query, BoostedDescriptionLibrary.class);
    List results = hibQuery.list();

    log.debug(hibQuery.explain(0).toString());
    log.debug(hibQuery.explain(1).toString());

    assertTrue(
        "incorrect document returned",
        ((BoostedDescriptionLibrary) results.get(0)).getDescription().startsWith("Martians"));

    // cleanup
    for (Object element :
        fullTextSession.createQuery("from " + BoostedDescriptionLibrary.class.getName()).list()) {
      fullTextSession.delete(element);
    }
    tx.commit();
    fullTextSession.close();
  }

  private void buildBoostedDescIndex(FullTextSession session) {
    Transaction tx = session.beginTransaction();
    BoostedDescriptionLibrary l = new BoostedDescriptionLibrary();
    l.setAuthor("H.G. Wells");
    l.setTitle("The Invisible Man");
    l.setDescription("Scientist discovers invisibility and becomes insane.");
    session.save(l);

    l = new BoostedDescriptionLibrary();
    l.setAuthor("H.G. Wells");
    l.setTitle("War of the Worlds");
    l.setDescription("Martians invade earth to eliminate mankind.");
    session.save(l);

    tx.commit();
  }

  private void buildBoostedFieldIndex(FullTextSession session) {
    Transaction tx = session.beginTransaction();
    BoostedFieldDescriptionLibrary l = new BoostedFieldDescriptionLibrary();
    l.setAuthor("H.G. Wells");
    l.setTitle("The Invisible Man");
    l.setDescription("Scientist discovers invisibility and becomes insane.");
    session.save(l);

    l = new BoostedFieldDescriptionLibrary();
    l.setAuthor("H.G. Wells");
    l.setTitle("War of the Worlds");
    l.setDescription("Martians invade earth to eliminate mankind.");
    session.save(l);

    tx.commit();
  }

  private void buildBoostedGetIndex(FullTextSession session) {
    Transaction tx = session.beginTransaction();
    BoostedGetDescriptionLibrary l = new BoostedGetDescriptionLibrary();
    l.setAuthor("H.G. Wells");
    l.setTitle("The Invisible Man");
    l.setDescription("Scientist discovers invisibility and becomes insane.");
    session.save(l);

    l = new BoostedGetDescriptionLibrary();
    l.setAuthor("H.G. Wells");
    l.setTitle("War of the Worlds");
    l.setDescription("Martians invade earth to eliminate mankind.");
    session.save(l);

    tx.commit();
  }

  protected Class<?>[] getAnnotatedClasses() {
    return new Class[] {
      BoostedDescriptionLibrary.class,
      BoostedFieldDescriptionLibrary.class,
      BoostedGetDescriptionLibrary.class,
    };
  }
}
/** package class extracting the SearchMappingFactory if needed */
public class SearchMappingBuilder {
  private static final Logger LOG = LoggerFactory.make();

  private SearchMappingBuilder() {}

  /**
   * This factory method takes a SearchConfiguration object and returns a SearchMapping object which
   * defines the programmatic model for indexing entities and fields.
   *
   * <p>Throws SearchException: 1) No @Factory found 2) Multiple @Factory found 3)
   * hibernate.search.model_mapping defines a class that cannot be found 4) Cannot invoke
   * the @Factory method to get new instance of SearchMapping
   *
   * @param cfg the search configuration
   * @return SearchMapping
   */
  public static SearchMapping getSearchMapping(SearchConfiguration cfg) {

    // try SearchConfiguration object first and then properties
    Object modelMappingProperty = cfg.getProgrammaticMapping();
    if (modelMappingProperty == null) {
      modelMappingProperty = cfg.getProperties().get(Environment.MODEL_MAPPING);
    }

    if (modelMappingProperty == null) {
      return null;
    }
    SearchMapping mapping = null;
    Object programmaticConfig = modelMappingProperty;
    if (programmaticConfig instanceof SearchMapping) {
      mapping = (SearchMapping) programmaticConfig;
      return mapping;
    }
    Class<?> clazz = getProgrammaticMappingClass(programmaticConfig);
    Method[] methods = clazz.getDeclaredMethods();
    int count = 0;
    for (Method method : methods) {
      if (method.isAnnotationPresent(Factory.class)) {
        count++;
        ReflectionHelper.setAccessible(method);
        mapping = getNewInstanceOfSearchMapping(clazz, method);
      }
    }
    validateMappingFactoryDefinition(count, clazz);
    return mapping;
  }

  private static SearchMapping getNewInstanceOfSearchMapping(Class<?> clazz, Method method) {
    SearchMapping mapping = null;
    try {
      LOG.debug(
          "invoking factory method [ {}.{} ] to get search mapping instance",
          clazz.getName(),
          method.getName());
      Object instance = clazz.newInstance();
      mapping = (SearchMapping) method.invoke(instance);
    } catch (Exception e) {
      throw new SearchException(
          "Unable to call the factory method: " + clazz.getName() + "." + method.getName(), e);
    }
    return mapping;
  }

  private static void validateMappingFactoryDefinition(int count, Class<?> factory) {
    if (count == 0) {
      throw new SearchException(
          "No @Factory method defined for building programmatic api on " + factory);
    }
    if (count > 1) {
      throw new SearchException(
          "Multiple @Factory methods defined. Only one factory method required. " + factory);
    }
  }

  private static Class<?> getProgrammaticMappingClass(Object programmaticConfig) {
    Class<?> clazz = null;
    if (programmaticConfig instanceof String) {
      final String className = (String) programmaticConfig;
      try {
        clazz = ReflectHelper.classForName(className, SearchMappingBuilder.class);
      } catch (ClassNotFoundException e) {
        throw new SearchException(
            "Unable to find " + Environment.MODEL_MAPPING + "=" + className, e);
      }
    } else if (programmaticConfig instanceof Class) {
      clazz = (Class<?>) programmaticConfig;
    } else {
      throw new SearchException(
          Environment.MODEL_MAPPING + " is of an unknown type: " + programmaticConfig.getClass());
    }
    if (clazz == null) {
      throw new SearchException("No programmatic factory defined");
    }
    return clazz;
  }
}
/**
 * @author Emmanuel Bernard
 * @author Sanne Grinovero
 * @author Hardy Ferentschik
 */
public final class DirectoryProviderHelper {
  private static final Logger log = LoggerFactory.make();

  private static final String ROOT_INDEX_PROP_NAME = "sourceBase";
  private static final String RELATIVE_INDEX_PROP_NAME = "source";
  private static final String COPY_BUFFER_SIZE_PROP_NAME = "buffer_size_on_copy";
  private static final String LOCKING_STRATEGY_PROP_NAME = "locking_strategy";
  private static final String FS_DIRECTORY_TYPE_PROP_NAME = "filesystem_access_type";
  private static final String INDEX_BASE_PROP_NAME = "indexBase";
  private static final String INDEX_NAME_PROP_NAME = "indexName";
  private static final String REFRESH_PROP_NAME = "refresh";

  private DirectoryProviderHelper() {}

  /**
   * Build a directory name out of a root and relative path, guessing the significant part and
   * checking for the file availability
   *
   * @param directoryProviderName
   * @param properties the configuration properties
   * @param needWritePermissions when true the directory will be tested for read-write permissions.
   * @return The file representing the source directory
   */
  public static File getSourceDirectory(
      String directoryProviderName, Properties properties, boolean needWritePermissions) {
    String root = properties.getProperty(ROOT_INDEX_PROP_NAME);
    String relative = properties.getProperty(RELATIVE_INDEX_PROP_NAME);
    File sourceDirectory;
    if (log.isTraceEnabled()) {
      log.trace(
          "Guess source directory from {} {} and {} {}",
          new Object[] {
            ROOT_INDEX_PROP_NAME,
            (root != null ? root : "<null>"),
            RELATIVE_INDEX_PROP_NAME,
            (relative != null ? relative : "<null>")
          });
    }
    if (relative == null) {
      relative = directoryProviderName;
    }
    if (StringHelper.isEmpty(root)) {
      log.debug("No root directory, go with relative " + relative);
      sourceDirectory = new File(relative);
      if (!sourceDirectory.isDirectory()) { // this also tests for existence
        throw new SearchException("Unable to read source directory: " + relative);
      }
      // else keep source as it
    } else {
      File rootDir = new File(root);
      makeSanityCheckedDirectory(rootDir, directoryProviderName, needWritePermissions);
      sourceDirectory = new File(root, relative);
      makeSanityCheckedDirectory(sourceDirectory, directoryProviderName, needWritePermissions);
      log.debug("Got directory from root + relative");
    }
    return sourceDirectory;
  }

  /**
   * Creates an FSDirectory in provided directory and initializes an index if not already existing.
   *
   * @param indexDir the directory where to write a new index
   * @param properties the configuration properties
   * @return the created {@code FSDirectory} instance
   * @throws IOException if an error
   */
  public static FSDirectory createFSIndex(File indexDir, Properties properties) throws IOException {
    LockFactory lockFactory = createLockFactory(indexDir, properties);
    FSDirectoryType fsDirectoryType = FSDirectoryType.getType(properties);
    FSDirectory fsDirectory = fsDirectoryType.getDirectory(indexDir, null);

    // must use the setter (instead of using the constructor) to set the lockFactory, or Lucene will
    // throw an exception if it's different than a previous setting.
    fsDirectory.setLockFactory(lockFactory);
    log.debug("Initialize index: '{}'", indexDir.getAbsolutePath());
    initializeIndexIfNeeded(fsDirectory);
    return fsDirectory;
  }

  /**
   * Initialize the Lucene Directory if it isn't already.
   *
   * @param directory the Directory to initialize
   * @throws SearchException in case of lock acquisition timeouts, IOException, or if a corrupt
   *     index is found
   */
  public static void initializeIndexIfNeeded(Directory directory) {
    try {
      if (!IndexReader.indexExists(directory)) {
        IndexWriter.MaxFieldLength fieldLength =
            new IndexWriter.MaxFieldLength(IndexWriter.DEFAULT_MAX_FIELD_LENGTH);
        IndexWriter iw = new IndexWriter(directory, new SimpleAnalyzer(), true, fieldLength);
        iw.close();
      }
    } catch (IOException e) {
      throw new SearchException("Could not initialize index", e);
    }
  }

  /**
   * Creates a LockFactory as selected in the configuration for the DirectoryProvider. The
   * SimpleFSLockFactory and NativeFSLockFactory need a File to know where to stock the filesystem
   * based locks; other implementations ignore this parameter.
   *
   * @param indexDir the directory to use to store locks, if needed by implementation
   * @param dirConfiguration the configuration of current DirectoryProvider
   * @return the LockFactory as configured, or a SimpleFSLockFactory in case of configuration errors
   *     or as a default.
   * @throws IOException
   */
  public static LockFactory createLockFactory(File indexDir, Properties dirConfiguration) {
    // For FS-based indexes default to "simple", default to "single" otherwise.
    String defaultStrategy = indexDir == null ? "single" : "simple";
    String lockFactoryName =
        dirConfiguration.getProperty(LOCKING_STRATEGY_PROP_NAME, defaultStrategy);
    if ("simple".equals(lockFactoryName)) {
      if (indexDir == null) {
        throw new SearchException(
            "To use \"simple\" as a LockFactory strategy an indexBase path must be set");
      }
      try {
        return new SimpleFSLockFactory(indexDir);
      } catch (IOException e) {
        throw new SearchException("Could not initialize SimpleFSLockFactory", e);
      }
    } else if ("native".equals(lockFactoryName)) {
      if (indexDir == null) {
        throw new SearchException(
            "To use \"native\" as a LockFactory strategy an indexBase path must be set");
      }
      try {
        return new NativeFSLockFactory(indexDir);
      } catch (IOException e) {
        throw new SearchException("Could not initialize NativeFSLockFactory", e);
      }
    } else if ("single".equals(lockFactoryName)) {
      return new SingleInstanceLockFactory();
    } else if ("none".equals(lockFactoryName)) {
      return new NoLockFactory();
    } else {
      LockFactoryFactory lockFactoryFactory =
          ClassLoaderHelper.instanceFromName(
              LockFactoryFactory.class,
              lockFactoryName,
              DirectoryProviderHelper.class,
              LOCKING_STRATEGY_PROP_NAME);
      return lockFactoryFactory.createLockFactory(indexDir, dirConfiguration);
    }
  }

  /**
   * Verify the index directory exists and is writable, or creates it if not existing.
   *
   * @param annotatedIndexName The index name declared on the @Indexed annotation
   * @param properties The properties may override the indexname.
   * @param verifyIsWritable Verify the directory is writable
   * @return the File representing the Index Directory
   * @throws SearchException
   */
  public static File getVerifiedIndexDir(
      String annotatedIndexName, Properties properties, boolean verifyIsWritable) {
    String indexBase = properties.getProperty(INDEX_BASE_PROP_NAME, ".");
    String indexName = properties.getProperty(INDEX_NAME_PROP_NAME, annotatedIndexName);
    File baseIndexDir = new File(indexBase);
    makeSanityCheckedDirectory(baseIndexDir, indexName, verifyIsWritable);
    File indexDir = new File(baseIndexDir, indexName);
    makeSanityCheckedDirectory(indexDir, indexName, verifyIsWritable);
    return indexDir;
  }

  /**
   * @param directory The directory to create or verify
   * @param indexName To label exceptions
   * @param verifyIsWritable Verify the directory is writable
   * @throws SearchException
   */
  private static void makeSanityCheckedDirectory(
      File directory, String indexName, boolean verifyIsWritable) {
    if (!directory.exists()) {
      log.warn("Index directory not found, creating: '" + directory.getAbsolutePath() + "'");
      // if not existing, create the full path
      if (!directory.mkdirs()) {
        throw new SearchException(
            "Unable to create index directory: "
                + directory.getAbsolutePath()
                + " for index "
                + indexName);
      }
    } else {
      // else check it is not a file
      if (!directory.isDirectory()) {
        throw new SearchException(
            "Unable to initialize index: "
                + indexName
                + ": "
                + directory.getAbsolutePath()
                + " is a file.");
      }
    }
    // and ensure it's writable
    if (verifyIsWritable && (!directory.canWrite())) {
      throw new SearchException(
          "Cannot write into index directory: "
              + directory.getAbsolutePath()
              + " for index "
              + indexName);
    }
  }

  static long getRefreshPeriod(Properties properties, String directoryProviderName) {
    String refreshPeriod = properties.getProperty(REFRESH_PROP_NAME, "3600");
    long period;
    try {
      period = Long.parseLong(refreshPeriod);
    } catch (NumberFormatException nfe) {
      throw new SearchException(
          "Unable to initialize index: "
              + directoryProviderName
              + "; refresh period is not numeric.",
          nfe);
    }
    log.debug("Refresh period: {} seconds", period);
    return period * 1000; // per second
  }

  /**
   * Users may configure the number of MB to use as "chunk size" for large file copy operations
   * performed by DirectoryProviders.
   *
   * @param directoryProviderName
   * @param properties the configuration properties
   * @return the number of Bytes to use as "chunk size" in file copy operations.
   */
  public static long getCopyBufferSize(String directoryProviderName, Properties properties) {
    String value = properties.getProperty(COPY_BUFFER_SIZE_PROP_NAME);
    long size = FileHelper.DEFAULT_COPY_BUFFER_SIZE;
    if (value != null) {
      try {
        size = Long.parseLong(value) * 1024 * 1024; // from MB to B.
      } catch (NumberFormatException nfe) {
        throw new SearchException(
            "Unable to initialize index "
                + directoryProviderName
                + "; "
                + COPY_BUFFER_SIZE_PROP_NAME
                + " is not numeric.",
            nfe);
      }
      if (size <= 0) {
        throw new SearchException(
            "Unable to initialize index "
                + directoryProviderName
                + "; "
                + COPY_BUFFER_SIZE_PROP_NAME
                + " needs to be greater than zero.");
      }
    }
    return size;
  }

  private enum FSDirectoryType {
    AUTO(null),
    SIMPLE(SimpleFSDirectory.class),
    NIO(NIOFSDirectory.class),
    MMAP(MMapDirectory.class);

    private Class<?> fsDirectoryClass;

    FSDirectoryType(Class<?> fsDirectoryClass) {
      this.fsDirectoryClass = fsDirectoryClass;
    }

    public FSDirectory getDirectory(File indexDir, LockFactory factory) throws IOException {
      FSDirectory directory;
      if (fsDirectoryClass == null) {
        directory = FSDirectory.open(indexDir, factory);
      } else {
        try {
          Constructor constructor = fsDirectoryClass.getConstructor(File.class, LockFactory.class);
          directory = (FSDirectory) constructor.newInstance(indexDir, factory);
        } catch (NoSuchMethodException e) {
          throw new SearchException("Unable to find appropriate FSDirectory constructor", e);
        } catch (InstantiationException e) {
          throw new SearchException(
              "Unable to instantiate FSDirectory class " + fsDirectoryClass.getName(), e);
        } catch (IllegalAccessException e) {
          throw new SearchException(
              "Unable to instantiate FSDirectory class " + fsDirectoryClass.getName(), e);
        } catch (InvocationTargetException e) {
          throw new SearchException(
              "Unable to instantiate FSDirectory class " + fsDirectoryClass.getName(), e);
        }
      }
      return directory;
    }

    public static FSDirectoryType getType(Properties properties) {
      FSDirectoryType fsDirectoryType;
      String fsDirectoryTypeValue = properties.getProperty(FS_DIRECTORY_TYPE_PROP_NAME);
      if (StringHelper.isNotEmpty(fsDirectoryTypeValue)) {
        try {
          fsDirectoryType = Enum.valueOf(FSDirectoryType.class, fsDirectoryTypeValue.toUpperCase());
        } catch (IllegalArgumentException e) {
          throw new SearchException(
              "Invalid option value for "
                  + FS_DIRECTORY_TYPE_PROP_NAME
                  + ": "
                  + fsDirectoryTypeValue);
        }
      } else {
        fsDirectoryType = AUTO;
      }
      return fsDirectoryType;
    }
  }
}