Save This Page
Home » hibernate-search-src-20081106 » org.hibernate » search » impl » [javadoc | source]
    1   //$Id: SearchFactoryImpl.java 15376 2008-10-23 02:15:50Z epbernard $
    2   package org.hibernate.search.impl;
    3   
    4   import java.beans.Introspector;
    5   import java.lang.reflect.Method;
    6   import java.util.ArrayList;
    7   import java.util.Collections;
    8   import java.util.HashMap;
    9   import java.util.HashSet;
   10   import java.util.Iterator;
   11   import java.util.List;
   12   import java.util.Map;
   13   import java.util.Properties;
   14   import java.util.Set;
   15   import java.util.WeakHashMap;
   16   import java.util.concurrent.atomic.AtomicBoolean;
   17   import java.util.concurrent.locks.ReentrantLock;
   18   
   19   import org.apache.lucene.analysis.Analyzer;
   20   import org.slf4j.Logger;
   21   
   22   import org.hibernate.annotations.common.reflection.ReflectionManager;
   23   import org.hibernate.annotations.common.reflection.XClass;
   24   import org.hibernate.annotations.common.reflection.java.JavaReflectionManager;
   25   import org.hibernate.annotations.common.util.StringHelper;
   26   import org.hibernate.search.Environment;
   27   import org.hibernate.search.SearchException;
   28   import org.hibernate.search.Version;
   29   import org.hibernate.search.annotations.Factory;
   30   import org.hibernate.search.annotations.FullTextFilterDef;
   31   import org.hibernate.search.annotations.FullTextFilterDefs;
   32   import org.hibernate.search.annotations.Indexed;
   33   import org.hibernate.search.annotations.Key;
   34   import org.hibernate.search.backend.BackendQueueProcessorFactory;
   35   import org.hibernate.search.backend.LuceneIndexingParameters;
   36   import org.hibernate.search.backend.LuceneWork;
   37   import org.hibernate.search.backend.OptimizeLuceneWork;
   38   import org.hibernate.search.backend.Worker;
   39   import org.hibernate.search.backend.WorkerFactory;
   40   import org.hibernate.search.backend.configuration.ConfigurationParseHelper;
   41   import org.hibernate.search.cfg.SearchConfiguration;
   42   import org.hibernate.search.engine.DocumentBuilder;
   43   import org.hibernate.search.engine.FilterDef;
   44   import org.hibernate.search.engine.SearchFactoryImplementor;
   45   import org.hibernate.search.engine.EntityState;
   46   import org.hibernate.search.filter.CachingWrapperFilter;
   47   import org.hibernate.search.filter.FilterCachingStrategy;
   48   import org.hibernate.search.filter.MRUFilterCachingStrategy;
   49   import org.hibernate.search.reader.ReaderProvider;
   50   import org.hibernate.search.reader.ReaderProviderFactory;
   51   import org.hibernate.search.store.DirectoryProvider;
   52   import org.hibernate.search.store.DirectoryProviderFactory;
   53   import org.hibernate.search.store.optimization.OptimizerStrategy;
   54   import org.hibernate.search.util.LoggerFactory;
   55   
   56   /**
   57    * @author Emmanuel Bernard
   58    */
   59   public class SearchFactoryImpl implements SearchFactoryImplementor {
   60   	private static final ThreadLocal<WeakHashMap<SearchConfiguration, SearchFactoryImpl>> contexts =
   61   			new ThreadLocal<WeakHashMap<SearchConfiguration, SearchFactoryImpl>>();
   62   
   63   	static {
   64   		Version.touch();
   65   	}
   66   
   67   	private static final Logger log = LoggerFactory.make();
   68   
   69   	private final Map<Class<?>, DocumentBuilder<?>> documentBuilders = new HashMap<Class<?>, DocumentBuilder<?>>();
   70   	private final Map<Class<?>, DocumentBuilder<?>> containedInOnlyBuilders = new HashMap<Class<?>, DocumentBuilder<?>>();
   71   	//keep track of the index modifiers per DirectoryProvider since multiple entity can use the same directory provider
   72   	private final Map<DirectoryProvider<?>, DirectoryProviderData> dirProviderData = new HashMap<DirectoryProvider<?>, DirectoryProviderData>();
   73   	private final Worker worker;
   74   	private final ReaderProvider readerProvider;
   75   	private BackendQueueProcessorFactory backendQueueProcessorFactory;
   76   	private final Map<String, FilterDef> filterDefinitions = new HashMap<String, FilterDef>();
   77   	private final FilterCachingStrategy filterCachingStrategy;
   78   	private Map<String, Analyzer> analyzers;
   79   	private final AtomicBoolean stopped = new AtomicBoolean( false );
   80   	private final int cacheBitResultsSize;
   81   	/*
   82   	 * used as a barrier (piggyback usage) between initialization and subsequent usage of searchFactory in different threads
   83   	 * this is due to our use of the initialize pattern is a few areas
   84   	 * subsequent reads on volatiles should be very cheap on most platform especially since we don't write after init
   85   	 *
   86   	 * This volatile is meant to be written after initialization
   87   	 * and read by all subsequent methods accessing the SearchFactory state
   88   	 * read to be as barrier != 0. If barrier == 0 we have a race condition, but is not likely to happen.
   89   	 */
   90   	private volatile short barrier;
   91   
   92   	/**
   93   	 * Each directory provider (index) can have its own performance settings.
   94   	 */
   95   	private Map<DirectoryProvider, LuceneIndexingParameters> dirProviderIndexingParams =
   96   		new HashMap<DirectoryProvider, LuceneIndexingParameters>();
   97   	private final String indexingStrategy;
   98   
   99   
  100   	public BackendQueueProcessorFactory getBackendQueueProcessorFactory() {
  101   		if (barrier != 0) { } //read barrier
  102   		return backendQueueProcessorFactory;
  103   	}
  104   
  105   	public void setBackendQueueProcessorFactory(BackendQueueProcessorFactory backendQueueProcessorFactory) {
  106   		//no need to set a barrier, we init in the same thread as the init one
  107   		this.backendQueueProcessorFactory = backendQueueProcessorFactory;
  108   	}
  109   
  110   	public SearchFactoryImpl(SearchConfiguration cfg) {
  111   		ReflectionManager reflectionManager = cfg.getReflectionManager();
  112   		if ( reflectionManager == null ) {
  113   			reflectionManager = new JavaReflectionManager();
  114   		}
  115   		this.indexingStrategy = defineIndexingStrategy( cfg ); //need to be done before the document builds
  116   		initDocumentBuilders( cfg, reflectionManager );
  117   
  118   		Set<Class<?>> indexedClasses = documentBuilders.keySet();
  119   		for (DocumentBuilder builder : documentBuilders.values()) {
  120   			builder.postInitialize( indexedClasses );
  121   		}
  122   		//not really necessary today
  123   		for (DocumentBuilder builder : containedInOnlyBuilders.values()) {
  124   			builder.postInitialize( indexedClasses );
  125   		}
  126   		this.worker = WorkerFactory.createWorker( cfg, this );
  127   		this.readerProvider = ReaderProviderFactory.createReaderProvider( cfg, this );
  128   		this.filterCachingStrategy = buildFilterCachingStrategy( cfg.getProperties() );
  129   		this.cacheBitResultsSize = ConfigurationParseHelper.getIntValue( cfg.getProperties(), Environment.CACHE_BIT_RESULT_SIZE, CachingWrapperFilter.DEFAULT_SIZE );
  130   		this.barrier = 1; //write barrier
  131   	}
  132   
  133   	private static String defineIndexingStrategy(SearchConfiguration cfg) {
  134   		String indexingStrategy = cfg.getProperties().getProperty( Environment.INDEXING_STRATEGY, "event" );
  135   		if ( ! ("event".equals( indexingStrategy ) || "manual".equals( indexingStrategy ) ) ) {
  136   			throw new SearchException( Environment.INDEXING_STRATEGY + " unknown: " + indexingStrategy );
  137   		}
  138   		return indexingStrategy;
  139   	}
  140   
  141   	public String getIndexingStrategy() {
  142   		if (barrier != 0) { } //read barrier
  143   		return indexingStrategy;
  144   	}
  145   
  146   	public void close() {
  147   		if (barrier != 0) { } //read barrier
  148   		if ( stopped.compareAndSet( false, true) ) {
  149   			try {
  150   				worker.close();
  151   			}
  152   			catch (Exception e) {
  153   				log.error( "Worker raises an exception on close()", e );
  154   			}
  155   
  156   			try {
  157   				readerProvider.destroy();
  158   			}
  159   			catch (Exception e) {
  160   				log.error( "ReaderProvider raises an exception on destroy()", e );
  161   			}
  162   
  163   			//TODO move to DirectoryProviderFactory for cleaner
  164   			for (DirectoryProvider dp : getDirectoryProviders() ) {
  165   				try {
  166   					dp.stop();
  167   				}
  168   				catch (Exception e) {
  169   					log.error( "DirectoryProvider raises an exception on stop() ", e );
  170   				}
  171   			}
  172   		}
  173   	}
  174   
  175   	public void addClassToDirectoryProvider(Class<?> clazz, DirectoryProvider<?> directoryProvider) {
  176   		//no need to set a read barrier, we only use this class in the init thread
  177   		DirectoryProviderData data = dirProviderData.get(directoryProvider);
  178   		if (data == null) {
  179   			data = new DirectoryProviderData();
  180   			dirProviderData.put( directoryProvider, data );
  181   		}
  182   		data.classes.add(clazz);
  183   	}
  184   
  185   	public Set<Class<?>> getClassesInDirectoryProvider(DirectoryProvider<?> directoryProvider) {
  186   		if (barrier != 0) { } //read barrier
  187   		return Collections.unmodifiableSet( dirProviderData.get(directoryProvider).classes );
  188   	}
  189   
  190   	private void bindFilterDefs(XClass mappedXClass) {
  191   		FullTextFilterDef defAnn = mappedXClass.getAnnotation( FullTextFilterDef.class );
  192   		if ( defAnn != null ) {
  193   			bindFilterDef( defAnn, mappedXClass );
  194   		}
  195   		FullTextFilterDefs defsAnn = mappedXClass.getAnnotation( FullTextFilterDefs.class );
  196   		if (defsAnn != null) {
  197   			for ( FullTextFilterDef def : defsAnn.value() ) {
  198   				bindFilterDef( def, mappedXClass );
  199   			}
  200   		}
  201   	}
  202   
  203   	private void bindFilterDef(FullTextFilterDef defAnn, XClass mappedXClass) {
  204   		if ( filterDefinitions.containsKey( defAnn.name() ) ) {
  205   			throw new SearchException("Multiple definition of @FullTextFilterDef.name=" + defAnn.name() + ": "
  206   					+ mappedXClass.getName() );
  207   		}
  208   
  209   		FilterDef filterDef = new FilterDef(defAnn);
  210   		try {
  211   			filterDef.getImpl().newInstance();
  212   		}
  213   		catch (IllegalAccessException e) {
  214   			throw new SearchException("Unable to create Filter class: " + filterDef.getImpl().getName(), e);
  215   		}
  216   		catch (InstantiationException e) {
  217   			throw new SearchException("Unable to create Filter class: " + filterDef.getImpl().getName(), e);
  218   		}
  219   		for ( Method method : filterDef.getImpl().getMethods() ) {
  220   			if ( method.isAnnotationPresent( Factory.class ) ) {
  221   				if ( filterDef.getFactoryMethod() != null ) {
  222   					throw new SearchException("Multiple @Factory methods found" + defAnn.name() + ": "
  223   							+ filterDef.getImpl().getName() + "." + method.getName() );
  224   				}
  225   				if ( !method.isAccessible() ) method.setAccessible( true );
  226   				filterDef.setFactoryMethod( method );
  227   			}
  228   			if ( method.isAnnotationPresent( Key.class ) ) {
  229   				if ( filterDef.getKeyMethod() != null ) {
  230   					throw new SearchException("Multiple @Key methods found" + defAnn.name() + ": "
  231   							+ filterDef.getImpl().getName() + "." + method.getName() );
  232   				}
  233   				if ( !method.isAccessible() ) method.setAccessible( true );
  234   				filterDef.setKeyMethod( method );
  235   			}
  236   
  237   			String name = method.getName();
  238   			if ( name.startsWith( "set" ) && method.getParameterTypes().length == 1 ) {
  239   				filterDef.addSetter( Introspector.decapitalize( name.substring( 3 ) ), method );
  240   			}
  241   		}
  242   		filterDefinitions.put( defAnn.name(), filterDef );
  243   	}
  244   
  245   
  246   	public Map<Class<?>, DocumentBuilder<?>> getDocumentBuilders() {
  247   		if (barrier != 0) { } //read barrier
  248   		return documentBuilders;
  249   	}
  250   
  251   	@SuppressWarnings( "unckecked" )
  252   	public <T> DocumentBuilder<T> getDocumentBuilder(Class<T> entityType) {
  253   		if (barrier != 0) { } //read barrier
  254   		return ( DocumentBuilder<T> ) documentBuilders.get( entityType );
  255   	}
  256   
  257   	@SuppressWarnings( "unckecked" )
  258   	public <T> DocumentBuilder<T> getContainedInOnlyBuilder(Class<T> entityType) {
  259   		if (barrier != 0) { } //read barrier
  260   		return ( DocumentBuilder<T> ) containedInOnlyBuilders.get( entityType );
  261   	}
  262   
  263   	public Set<DirectoryProvider<?>> getDirectoryProviders() {
  264   		if (barrier != 0) { } //read barrier
  265   		return this.dirProviderData.keySet();
  266   	}
  267   
  268   	public Worker getWorker() {
  269   		if (barrier != 0) { } //read barrier
  270   		return worker;
  271   	}
  272   
  273   	public void addOptimizerStrategy(DirectoryProvider<?> provider, OptimizerStrategy optimizerStrategy) {
  274   		//no need to set a read barrier, we run this method on the init thread
  275   		DirectoryProviderData data = dirProviderData.get(provider);
  276   		if (data == null) {
  277   			data = new DirectoryProviderData();
  278   			dirProviderData.put( provider, data );
  279   		}
  280   		data.optimizerStrategy = optimizerStrategy;
  281   	}
  282   
  283   	public void addIndexingParameters(DirectoryProvider<?> provider, LuceneIndexingParameters indexingParams) {
  284   		//no need to set a read barrier, we run this method on the init thread
  285   		dirProviderIndexingParams.put( provider, indexingParams );
  286   	}
  287   
  288   	public OptimizerStrategy getOptimizerStrategy(DirectoryProvider<?> provider) {
  289   		if (barrier != 0) {} //read barrier
  290   		return dirProviderData.get( provider ).optimizerStrategy;
  291   	}
  292   
  293   	public LuceneIndexingParameters getIndexingParameters(DirectoryProvider<?> provider ) {
  294   		if (barrier != 0) {} //read barrier
  295   		return dirProviderIndexingParams.get( provider );
  296   	}
  297   
  298   	public ReaderProvider getReaderProvider() {
  299   		if (barrier != 0) {} //read barrier
  300   		return readerProvider;
  301   	}
  302   
  303   	public DirectoryProvider[] getDirectoryProviders(Class<?> entity) {
  304   		if (barrier != 0) {} //read barrier
  305   		DocumentBuilder<?> documentBuilder = getDocumentBuilder( entity );
  306   		return documentBuilder == null ? null : documentBuilder.getDirectoryProviders();
  307   	}
  308   
  309   	public void optimize() {
  310   		if (barrier != 0) {} //read barrier
  311   		Set<Class<?>> clazzs = getDocumentBuilders().keySet();
  312   		for (Class clazz : clazzs) {
  313   			optimize( clazz );
  314   		}
  315   	}
  316   
  317   	public void optimize(Class entityType) {
  318   		if (barrier != 0) {} //read barrier
  319   		if ( ! getDocumentBuilders().containsKey( entityType ) ) {
  320   			throw new SearchException("Entity not indexed: " + entityType);
  321   		}
  322   		List<LuceneWork> queue = new ArrayList<LuceneWork>(1);
  323   		queue.add( new OptimizeLuceneWork( entityType ) );
  324   		getBackendQueueProcessorFactory().getProcessor( queue ).run();
  325   	}
  326   
  327   	public Analyzer getAnalyzer(String name) {
  328   		if (barrier != 0) {} //read barrier
  329   		final Analyzer analyzer = analyzers.get( name );
  330   		if ( analyzer == null) throw new SearchException( "Unknown Analyzer definition: " + name);
  331   		return analyzer;
  332   	}
  333   	
  334   	public Analyzer getAnalyzer(Class clazz) {
  335   		if ( clazz ==  null) {
  336   			throw new IllegalArgumentException( "A class has to be specified for retrieving a scoped analyzer" );
  337   		}
  338   		
  339   		DocumentBuilder<?> builder = documentBuilders.get( clazz );
  340   		if ( builder == null ) {
  341   			throw new IllegalArgumentException( "Entity for which to retrieve the scoped analyzer is not an @Indexed entity: " + clazz.getName() );
  342   		}
  343   		
  344   		return builder.getAnalyzer();
  345   	}	
  346   
  347   	private void initDocumentBuilders(SearchConfiguration cfg, ReflectionManager reflectionManager) {
  348   		InitContext context = new InitContext( cfg );
  349   		Iterator<Class<?>> iter = cfg.getClassMappings();
  350   		DirectoryProviderFactory factory = new DirectoryProviderFactory();
  351   
  352   		while ( iter.hasNext() ) {
  353   			Class mappedClass = iter.next();
  354   			if (mappedClass != null) {
  355   				XClass mappedXClass = reflectionManager.toXClass(mappedClass);
  356   				if ( mappedXClass != null) {
  357   					if ( mappedXClass.isAnnotationPresent( Indexed.class ) ) {
  358   						DirectoryProviderFactory.DirectoryProviders providers = factory.createDirectoryProviders( mappedXClass, cfg, this, reflectionManager );
  359   						//FIXME DocumentBuilder needs to be built by a helper method receiving Class<T> to infer T properly
  360   						//XClass unfortunately is not (yet) genericized: TODO?
  361   						final DocumentBuilder<?> documentBuilder = new DocumentBuilder(
  362   								mappedXClass, context, providers.getProviders(), providers.getSelectionStrategy(),
  363   								reflectionManager
  364   						);
  365   
  366   						documentBuilders.put( mappedClass, documentBuilder );
  367   					}
  368   					else {
  369   						//FIXME DocumentBuilder needs to be built by a helper method receiving Class<T> to infer T properly
  370   						//XClass unfortunately is not (yet) genericized: TODO?
  371   						final DocumentBuilder<?> documentBuilder = new DocumentBuilder(
  372   								mappedXClass, context, reflectionManager
  373   						);
  374   						//TODO enhance that, I don't like to expose EntityState
  375   						if ( documentBuilder.getEntityState() != EntityState.NON_INDEXABLE ) {
  376   							containedInOnlyBuilders.put( mappedClass, documentBuilder );
  377   						}
  378   					}
  379   					bindFilterDefs(mappedXClass);
  380   					//TODO should analyzer def for classes at tyher sqme level???
  381   				}
  382   			}
  383   		}
  384   		analyzers = context.initLazyAnalyzers();
  385   		factory.startDirectoryProviders();
  386   	}
  387   
  388   	private static FilterCachingStrategy buildFilterCachingStrategy(Properties properties) {
  389   		FilterCachingStrategy filterCachingStrategy;
  390   		String impl = properties.getProperty( Environment.FILTER_CACHING_STRATEGY );
  391   		if ( StringHelper.isEmpty( impl ) || "mru".equalsIgnoreCase( impl ) ) {
  392   			filterCachingStrategy = new MRUFilterCachingStrategy();
  393   		}
  394   		else {
  395   			try {
  396   				Class filterCachingStrategyClass = org.hibernate.annotations.common.util.ReflectHelper.classForName( impl, SearchFactoryImpl.class );
  397   				filterCachingStrategy = (FilterCachingStrategy) filterCachingStrategyClass.newInstance();
  398   			}
  399   			catch (ClassNotFoundException e) {
  400   				throw new SearchException( "Unable to find filterCachingStrategy class: " + impl, e );
  401   			}
  402   			catch (IllegalAccessException e) {
  403   				throw new SearchException( "Unable to instantiate filterCachingStrategy class: " + impl, e );
  404   			}
  405   			catch (InstantiationException e) {
  406   				throw new SearchException( "Unable to instantiate filterCachingStrategy class: " + impl, e );
  407   			}
  408   		}
  409   		filterCachingStrategy.initialize( properties );
  410   		return filterCachingStrategy;
  411   	}
  412   
  413   	public FilterCachingStrategy getFilterCachingStrategy() {
  414   		if (barrier != 0) {} //read barrier
  415   		return filterCachingStrategy;
  416   	}
  417   
  418   	public FilterDef getFilterDefinition(String name) {
  419   		if (barrier != 0) {} //read barrier
  420   		return filterDefinitions.get( name );
  421   	}
  422   
  423   	private static class DirectoryProviderData {
  424   		public final ReentrantLock dirLock = new ReentrantLock();
  425   		public OptimizerStrategy optimizerStrategy;
  426   		public Set<Class<?>> classes = new HashSet<Class<?>>(2);
  427   	}
  428   
  429   	public ReentrantLock getDirectoryProviderLock(DirectoryProvider<?> dp) {
  430   		if (barrier != 0) {} //read barrier
  431   		return this.dirProviderData.get( dp ).dirLock;
  432   	}
  433   
  434   	public void addDirectoryProvider(DirectoryProvider<?> provider) {
  435   		//no need to set a barrier we use this method in the init thread
  436   		this.dirProviderData.put( provider, new DirectoryProviderData() );
  437   	}
  438   
  439   	public int getFilterCacheBitResultsSize() {
  440   		if (barrier != 0) {} //read barrier
  441   		return cacheBitResultsSize;
  442   	}
  443   }

Save This Page
Home » hibernate-search-src-20081106 » org.hibernate » search » impl » [javadoc | source]