/** @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; } } }