Package org.olat.search.service.searcher

Source Code of org.olat.search.service.searcher.Search

/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/

package org.olat.search.service.searcher;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Set;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.olat.core.commons.services.search.OlatDocument;
import org.olat.core.commons.services.search.QueryException;
import org.olat.core.commons.services.search.SearchResults;
import org.olat.core.commons.services.search.ServiceNotAvailableException;
import org.olat.core.id.Identity;
import org.olat.core.id.Roles;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.ArrayHelper;
import org.olat.search.service.SearchMetadataFieldsProvider;
import org.olat.search.service.SearchServiceImpl;
import org.olat.search.service.spell.SearchSpellChecker;

/**
* Search part inside of search-service.
* @author Christian Guretzki
*/
public class Search implements OLATSearcher {
 
  private OLog log = Tracing.createLoggerFor(this.getClass());
 
  private String indexPath;
  private Analyzer analyzer;
  private Searcher searcher;
  private SearchSpellChecker searchSpellChecker;
 
  /** Counts number of search queries since last restart. */
  private long queryCount = 0;
  private Date openIndexDate;
  private Object createIndexSearcherLock = new Object();
  private long maxIndexTime;
 
   
  public void setMaxIndexTime(long maxIndexTime) {
    this.maxIndexTime = maxIndexTime;
  }

  private String fields[] = {OlatDocument.TITLE_FIELD_NAME,OlatDocument.DESCRIPTION_FIELD_NAME,OlatDocument.CONTENT_FIELD_NAME,OlatDocument.AUTHOR_FIELD_NAME};

 
  public Search() {
    // called by spring.
  }
 
  /**
   * [used by spring]
   *
   */
  public void init() {
    // called by spring.
  }
 
  /**
   * [used by spring]
   * @param indexPath the absolute file-path to search index directory.
   */
  public void setIndexPath(String indexPath) {
    this.indexPath = indexPath;   
    analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT);
    try {
      createIndexSearcher(indexPath);
      checkIsIndexUpToDate();
    } catch (IOException e) {
      log.info("Can not create IndexSearcher at startup");
    }   
  }
 
  /**
   * Do search a certain query. The results will be filtered for the identity and roles.
   * @param queryString   Search query-string.
   * @param identity      Filter results for this identity (user).
   * @param roles         Filter results for this roles (role of user).
    * @return              SearchResults object for this query
   */
  public SearchResults doSearch(String queryString, Identity identity, Roles roles, boolean doHighlighting) throws ServiceNotAvailableException, ParseException, QueryException {
    try {
      if (!existIndex()) {
        log.warn("Index does not exist, can't search for queryString: "+queryString);
        throw new ServiceNotAvailableException("Index does not exist");
      }
      synchronized (createIndexSearcherLock) {//o_clusterOK by:fj if service is only configured on one vm, which is recommended way
        if (searcher == null) {
          try {
            createIndexSearcher(indexPath);
            checkIsIndexUpToDate();
          } catch(IOException ioEx) {
            log.warn("Can not create searcher", ioEx);
            throw new ServiceNotAvailableException("Index is not available");
          }
        }
        if ( hasNewerIndexFile() ) {
          reopenIndexSearcher();
          checkIsIndexUpToDate();
        }     
      }
      log.info("queryString=" + queryString);
     
      QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_CURRENT, fields, analyzer);
      queryParser.setLowercaseExpandedTerms(false);//some add. fields are not tokenized and not lowered case
      Query query = queryParser.parse(queryString);
      try {
        query = searcher.rewrite(query);
      } catch (Exception ex) {
        throw new QueryException("Rewrite-Exception query because too many clauses. Query=" + query);
      }
      long startTime = System.currentTimeMillis();
      int n = SearchServiceImpl.getInstance().getSearchModuleConfig().getMaxHits() + 1;
      TopDocs docs = searcher.search(query, n);
      long queryTime = System.currentTimeMillis() - startTime;
      if (log.isDebug()) log.debug("hits.length()=" + docs.totalHits);
      SearchResultsImpl searchResult = new SearchResultsImpl(searcher, docs, query, analyzer, identity, roles, doHighlighting);
      searchResult.setQueryTime(queryTime);
      searchResult.setNumberOfIndexDocuments(searcher.maxDoc());
      queryCount++;
      return searchResult;
    } catch (ServiceNotAvailableException naex) {
      // pass exception
      throw new ServiceNotAvailableException(naex.getMessage());
    } catch (ParseException pex) {
      throw new ParseException("can not parse query=" + queryString);
    } catch (QueryException qex) {
      throw new QueryException(qex.getMessage());
    } catch (Exception ex) {
      log.warn("Exception in search", ex);
      throw new ServiceNotAvailableException(ex.getMessage());
    }
   }

  private void checkIsIndexUpToDate() throws IOException {
    long indexTime = getCurrentIndexDate().getTime();
    long currentTime = System.currentTimeMillis();
    if ( (currentTime - indexTime ) > maxIndexTime) {
      log.error("Search index is too old indexDate=" + getCurrentIndexDate());
    }
  }
 
  public long getQueryCount() {
    return queryCount;
  }
 
  public void stop() {
    try {
      if (searcher != null) {
        searcher.close();
        searcher = null;
      }
    } catch (IOException e) {
      log.error("", e);
    }
  }

 
  private boolean hasNewerIndexFile() {
    try {
      if (getCurrentIndexDate().after(openIndexDate) ) {
        return true;
      }
    } catch (IOException e) { // no index file exist
    }
    return false;
  }

  private void createIndexSearcher(String path) throws IOException {
    File indexFile = new File(path);
    Directory directory = FSDirectory.open(indexFile);
    searcher = new IndexSearcher(directory);
    openIndexDate = getCurrentIndexDate();
  }

  private void reopenIndexSearcher() {
    if ( hasNewerIndexFile() ) {
      log.debug("New index file available, reopen it");
      try {
        searcher.close();
        createIndexSearcher(indexPath);
      } catch (IOException e) {
        log.warn("Could not reopen index-searcher", e);
      }
    }
  }

  /**
   * @return  Creation date of current used search index.
   */
  private Date getCurrentIndexDate() throws IOException {
    File indexFile = new File(indexPath);
    Directory directory = FSDirectory.open(indexFile);
    return new Date(IndexReader.getCurrentVersion(directory));
  }

  /**
   * Check if index exist.
   * @return true : Index exists.
   */
  private boolean existIndex()
  throws IOException {
    try {
      File indexFile = new File(indexPath);
      Directory directory = FSDirectory.open(indexFile);
      return IndexReader.indexExists(directory);
    } catch (IOException e) {
      throw e;
    }
  }
 
  /**
   * Sets the SearchSpellChecker delegate.
   * @param searchSpellChecker
   */
  public void setSearchSpellChecker(SearchSpellChecker searchSpellChecker) {
    this.searchSpellChecker = searchSpellChecker;
  }
 
  /**
   * Delegates impl to the searchSpellChecker.
   * @see org.olat.search.service.searcher.OLATSearcher#spellCheck(java.lang.String)
   */
  public Set<String> spellCheck(String query) {
    if(searchSpellChecker==null) throw new AssertException ("Try to call spellCheck() in Search.java but searchSpellChecker is null");
    return searchSpellChecker.check(query);
  }

  /**
   * Spring setter to inject the available metadata
   *
   * @param metadataFields
   */
  public void setMetadataFields(SearchMetadataFieldsProvider metadataFields) {
    if (metadataFields != null) {
      // add metadata fields to normal fields
      String[] metaFields = ArrayHelper.toArray(metadataFields.getAdvancedSearchableFields());   
      String[] newFields = new String[this.fields.length + metaFields.length];
      System.arraycopy(this.fields, 0, newFields, 0, this.fields.length);
      System.arraycopy(metaFields, 0, newFields, this.fields.length, metaFields.length);
      this.fields = newFields;     
    }
  }

}
TOP

Related Classes of org.olat.search.service.searcher.Search

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.