| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| package org.apache.wiki.search; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.lang.reflect.Constructor; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.Properties; |
| import java.util.Vector; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.log4j.Logger; |
| import org.apache.lucene.analysis.Analyzer; |
| import org.apache.lucene.analysis.TokenStream; |
| import org.apache.lucene.document.Document; |
| import org.apache.lucene.document.Field; |
| import org.apache.lucene.document.StringField; |
| import org.apache.lucene.document.TextField; |
| import org.apache.lucene.index.CorruptIndexException; |
| import org.apache.lucene.index.DirectoryReader; |
| import org.apache.lucene.index.IndexReader; |
| import org.apache.lucene.index.IndexWriter; |
| import org.apache.lucene.index.IndexWriterConfig; |
| import org.apache.lucene.index.IndexWriterConfig.OpenMode; |
| import org.apache.lucene.index.Term; |
| import org.apache.lucene.queryparser.classic.MultiFieldQueryParser; |
| import org.apache.lucene.queryparser.classic.ParseException; |
| import org.apache.lucene.queryparser.classic.QueryParser; |
| import org.apache.lucene.search.IndexSearcher; |
| import org.apache.lucene.search.Query; |
| import org.apache.lucene.search.ScoreDoc; |
| import org.apache.lucene.search.TermQuery; |
| import org.apache.lucene.search.highlight.Highlighter; |
| import org.apache.lucene.search.highlight.InvalidTokenOffsetsException; |
| import org.apache.lucene.search.highlight.QueryScorer; |
| import org.apache.lucene.search.highlight.SimpleHTMLEncoder; |
| import org.apache.lucene.search.highlight.SimpleHTMLFormatter; |
| import org.apache.lucene.store.Directory; |
| import org.apache.lucene.store.LockObtainFailedException; |
| import org.apache.lucene.store.SimpleFSDirectory; |
| import org.apache.lucene.util.Version; |
| import org.apache.wiki.InternalWikiException; |
| import org.apache.wiki.WatchDog; |
| import org.apache.wiki.WikiBackgroundThread; |
| import org.apache.wiki.WikiContext; |
| import org.apache.wiki.WikiEngine; |
| import org.apache.wiki.WikiPage; |
| import org.apache.wiki.WikiProvider; |
| import org.apache.wiki.api.exceptions.NoRequiredPropertyException; |
| import org.apache.wiki.api.exceptions.ProviderException; |
| import org.apache.wiki.api.exceptions.WikiException; |
| import org.apache.wiki.attachment.Attachment; |
| import org.apache.wiki.attachment.AttachmentManager; |
| import org.apache.wiki.auth.AuthorizationManager; |
| import org.apache.wiki.auth.permissions.PagePermission; |
| import org.apache.wiki.parser.MarkupParser; |
| import org.apache.wiki.providers.WikiPageProvider; |
| import org.apache.wiki.util.ClassUtil; |
| import org.apache.wiki.util.FileUtil; |
| import org.apache.wiki.util.TextUtil; |
| |
| |
| /** |
| * Interface for the search providers that handle searching the Wiki |
| * |
| * @since 2.2.21. |
| */ |
| public class LuceneSearchProvider implements SearchProvider { |
| |
| protected static final Logger log = Logger.getLogger(LuceneSearchProvider.class); |
| |
| private WikiEngine m_engine; |
| |
| // Lucene properties. |
| |
| /** Which analyzer to use. Default is StandardAnalyzer. */ |
| public static final String PROP_LUCENE_ANALYZER = "jspwiki.lucene.analyzer"; |
| |
| private static final String PROP_LUCENE_INDEXDELAY = "jspwiki.lucene.indexdelay"; |
| private static final String PROP_LUCENE_INITIALDELAY = "jspwiki.lucene.initialdelay"; |
| |
| private String m_analyzerClass = "org.apache.lucene.analysis.standard.ClassicAnalyzer"; |
| |
| private static final String LUCENE_DIR = "lucene"; |
| |
| /** These attachment file suffixes will be indexed. */ |
| public static final String[] SEARCHABLE_FILE_SUFFIXES = new String[] { ".txt", ".ini", ".xml", ".html", "htm", ".mm", ".htm", |
| ".xhtml", ".java", ".c", ".cpp", ".php", ".asm", ".sh", |
| ".properties", ".kml", ".gpx", ".loc" }; |
| |
| protected static final String LUCENE_ID = "id"; |
| protected static final String LUCENE_PAGE_CONTENTS = "contents"; |
| protected static final String LUCENE_AUTHOR = "author"; |
| protected static final String LUCENE_ATTACHMENTS = "attachment"; |
| protected static final String LUCENE_PAGE_NAME = "name"; |
| |
| private String m_luceneDirectory; |
| protected Vector<Object[]> m_updates = new Vector<Object[]>(); // Vector because multi-threaded. |
| |
| /** Maximum number of fragments from search matches. */ |
| private static final int MAX_FRAGMENTS = 3; |
| |
| /** The maximum number of hits to return from searches. */ |
| public static final int MAX_SEARCH_HITS = 99999; |
| |
| private static String c_punctuationSpaces = StringUtils.repeat(" ", MarkupParser.PUNCTUATION_CHARS_ALLOWED.length() ); |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void initialize(WikiEngine engine, Properties props) |
| throws NoRequiredPropertyException, WikiException |
| { |
| m_engine = engine; |
| |
| m_luceneDirectory = engine.getWorkDir()+File.separator+LUCENE_DIR; |
| |
| int initialDelay = TextUtil.getIntegerProperty( props, PROP_LUCENE_INITIALDELAY, LuceneUpdater.INITIAL_DELAY ); |
| int indexDelay = TextUtil.getIntegerProperty( props, PROP_LUCENE_INDEXDELAY, LuceneUpdater.INDEX_DELAY ); |
| |
| m_analyzerClass = TextUtil.getStringProperty( props, PROP_LUCENE_ANALYZER, m_analyzerClass ); |
| // FIXME: Just to be simple for now, we will do full reindex |
| // only if no files are in lucene directory. |
| |
| File dir = new File(m_luceneDirectory); |
| |
| log.info("Lucene enabled, cache will be in: "+dir.getAbsolutePath()); |
| |
| try |
| { |
| if( !dir.exists() ) |
| { |
| dir.mkdirs(); |
| } |
| |
| if( !dir.exists() || !dir.canWrite() || !dir.canRead() ) |
| { |
| log.error("Cannot write to Lucene directory, disabling Lucene: "+dir.getAbsolutePath()); |
| throw new IOException( "Invalid Lucene directory." ); |
| } |
| |
| String[] filelist = dir.list(); |
| |
| if( filelist == null ) |
| { |
| throw new IOException( "Invalid Lucene directory: cannot produce listing: "+dir.getAbsolutePath()); |
| } |
| } |
| catch ( IOException e ) |
| { |
| log.error("Problem while creating Lucene index - not using Lucene.", e); |
| } |
| |
| // Start the Lucene update thread, which waits first |
| // for a little while before starting to go through |
| // the Lucene "pages that need updating". |
| LuceneUpdater updater = new LuceneUpdater( m_engine, this, initialDelay, indexDelay ); |
| updater.start(); |
| } |
| |
| /** |
| * Returns the handling engine. |
| * |
| * @return Current WikiEngine |
| */ |
| protected WikiEngine getEngine() |
| { |
| return m_engine; |
| } |
| |
| /** |
| * Performs a full Lucene reindex, if necessary. |
| * |
| * @throws IOException If there's a problem during indexing |
| */ |
| protected void doFullLuceneReindex() |
| throws IOException |
| { |
| File dir = new File(m_luceneDirectory); |
| |
| String[] filelist = dir.list(); |
| |
| if( filelist == null ) |
| { |
| throw new IOException( "Invalid Lucene directory: cannot produce listing: "+dir.getAbsolutePath()); |
| } |
| |
| try |
| { |
| if( filelist.length == 0 ) |
| { |
| // |
| // No files? Reindex! |
| // |
| Date start = new Date(); |
| IndexWriter writer = null; |
| |
| log.info("Starting Lucene reindexing, this can take a couple of minutes..."); |
| |
| Directory luceneDir = new SimpleFSDirectory(dir, null); |
| |
| try |
| { |
| writer = getIndexWriter( luceneDir ); |
| Collection allPages = m_engine.getPageManager().getAllPages(); |
| |
| for( Iterator iterator = allPages.iterator(); iterator.hasNext(); ) |
| { |
| WikiPage page = (WikiPage) iterator.next(); |
| |
| try |
| { |
| String text = m_engine.getPageManager().getPageText( page.getName(), |
| WikiProvider.LATEST_VERSION ); |
| luceneIndexPage( page, text, writer ); |
| } |
| catch( IOException e ) |
| { |
| log.warn( "Unable to index page " + page.getName() + ", continuing to next ", e ); |
| } |
| } |
| |
| Collection allAttachments = m_engine.getAttachmentManager().getAllAttachments(); |
| for( Iterator iterator = allAttachments.iterator(); iterator.hasNext(); ) |
| { |
| Attachment att = (Attachment) iterator.next(); |
| |
| try |
| { |
| String text = getAttachmentContent( att.getName(), WikiProvider.LATEST_VERSION ); |
| luceneIndexPage( att, text, writer ); |
| } |
| catch( IOException e ) |
| { |
| log.warn( "Unable to index attachment " + att.getName() + ", continuing to next", e ); |
| } |
| } |
| |
| } |
| finally |
| { |
| close( writer ); |
| } |
| |
| Date end = new Date(); |
| log.info( "Full Lucene index finished in " + (end.getTime() - start.getTime()) + " milliseconds." ); |
| } |
| else |
| { |
| log.info("Files found in Lucene directory, not reindexing."); |
| } |
| } |
| catch( NoClassDefFoundError e ) |
| { |
| log.info("Lucene libraries do not exist - not using Lucene."); |
| } |
| catch ( IOException e ) |
| { |
| log.error("Problem while creating Lucene index - not using Lucene.", e); |
| } |
| catch ( ProviderException e ) |
| { |
| log.error("Problem reading pages while creating Lucene index (JSPWiki won't start.)", e); |
| throw new IllegalArgumentException("unable to create Lucene index"); |
| } |
| catch( Exception e ) |
| { |
| log.error("Unable to start lucene",e); |
| } |
| |
| } |
| |
| /** |
| * Fetches the attachment content from the repository. |
| * Content is flat text that can be used for indexing/searching or display |
| * |
| * @param attachmentName Name of the attachment. |
| * @param version The version of the attachment. |
| * |
| * @return the content of the Attachment as a String. |
| */ |
| protected String getAttachmentContent( String attachmentName, int version ) |
| { |
| AttachmentManager mgr = m_engine.getAttachmentManager(); |
| |
| try |
| { |
| Attachment att = mgr.getAttachmentInfo( attachmentName, version ); |
| //FIXME: Find out why sometimes att is null |
| if(att != null) |
| { |
| return getAttachmentContent( att ); |
| } |
| } |
| catch (ProviderException e) |
| { |
| log.error("Attachment cannot be loaded", e); |
| } |
| // Something was wrong, no result is returned. |
| return null; |
| } |
| |
| /** |
| * @param att Attachment to get content for. Filename extension is used to determine the type of the attachment. |
| * @return String representing the content of the file. |
| * FIXME This is a very simple implementation of some text-based attachment, mainly used for testing. |
| * This should be replaced /moved to Attachment search providers or some other 'pluggable' wat to search attachments |
| */ |
| protected String getAttachmentContent( Attachment att ) |
| { |
| AttachmentManager mgr = m_engine.getAttachmentManager(); |
| //FIXME: Add attachment plugin structure |
| |
| String filename = att.getFileName(); |
| |
| boolean searchSuffix = false; |
| for( String suffix : SEARCHABLE_FILE_SUFFIXES ) |
| { |
| if( filename.endsWith( suffix ) ) |
| { |
| searchSuffix = true; |
| } |
| } |
| |
| String out = null; |
| if( searchSuffix ) |
| { |
| InputStream attStream = null; |
| StringWriter sout = new StringWriter(); |
| |
| try |
| { |
| attStream = mgr.getAttachmentStream( att ); |
| FileUtil.copyContents( new InputStreamReader(attStream), sout ); |
| out = sout.toString(); |
| } |
| catch (ProviderException e) |
| { |
| log.error("Attachment cannot be loaded", e); |
| } |
| catch (IOException e) |
| { |
| log.error("Attachment cannot be loaded", e); |
| } |
| finally |
| { |
| IOUtils.closeQuietly( attStream ); |
| IOUtils.closeQuietly( sout ); |
| } |
| } |
| |
| return out; |
| } |
| |
| /** |
| * Updates the lucene index for a single page. |
| * |
| * @param page The WikiPage to check |
| * @param text The page text to index. |
| */ |
| protected synchronized void updateLuceneIndex( WikiPage page, String text ) |
| { |
| IndexWriter writer = null; |
| |
| log.debug("Updating Lucene index for page '" + page.getName() + "'..."); |
| |
| Directory luceneDir = null; |
| try |
| { |
| pageRemoved( page ); |
| |
| // Now add back the new version. |
| luceneDir = new SimpleFSDirectory(new File(m_luceneDirectory), null); |
| writer = getIndexWriter( luceneDir ); |
| |
| luceneIndexPage( page, text, writer ); |
| } |
| catch ( IOException e ) |
| { |
| log.error("Unable to update page '" + page.getName() + "' from Lucene index", e); |
| // reindexPage( page ); |
| } |
| catch( Exception e ) |
| { |
| log.error("Unexpected Lucene exception - please check configuration!",e); |
| // reindexPage( page ); |
| } |
| finally |
| { |
| close( writer ); |
| } |
| |
| log.debug("Done updating Lucene index for page '" + page.getName() + "'."); |
| } |
| |
| |
| private Analyzer getLuceneAnalyzer() throws ProviderException |
| { |
| try |
| { |
| Class< ? > clazz = ClassUtil.findClass( "", m_analyzerClass ); |
| Constructor< ? > constructor = clazz.getConstructor( Version.LUCENE_47.getClass() ); |
| Analyzer analyzer = (Analyzer) constructor.newInstance( Version.LUCENE_47 ); |
| return analyzer; |
| } |
| catch( Exception e ) |
| { |
| String msg = "Could not get LuceneAnalyzer class " + m_analyzerClass + ", reason: "; |
| log.error( msg, e ); |
| throw new ProviderException( msg + e ); |
| } |
| } |
| |
| /** |
| * Indexes page using the given IndexWriter. |
| * |
| * @param page WikiPage |
| * @param text Page text to index |
| * @param writer The Lucene IndexWriter to use for indexing |
| * @return the created index Document |
| * @throws IOException If there's an indexing problem |
| */ |
| protected Document luceneIndexPage( WikiPage page, String text, IndexWriter writer ) |
| throws IOException |
| { |
| if( log.isDebugEnabled() ) log.debug( "Indexing "+page.getName()+"..." ); |
| |
| // make a new, empty document |
| Document doc = new Document(); |
| |
| if( text == null ) return doc; |
| |
| // Raw name is the keyword we'll use to refer to this document for updates. |
| Field field = new Field( LUCENE_ID, page.getName(), StringField.TYPE_STORED ); |
| doc.add( field ); |
| |
| // Body text. It is stored in the doc for search contexts. |
| field = new Field( LUCENE_PAGE_CONTENTS, text, TextField.TYPE_STORED ); |
| doc.add( field ); |
| |
| // Allow searching by page name. Both beautified and raw |
| String unTokenizedTitle = StringUtils.replaceChars( page.getName(), |
| MarkupParser.PUNCTUATION_CHARS_ALLOWED, |
| c_punctuationSpaces ); |
| |
| field = new Field( LUCENE_PAGE_NAME, |
| TextUtil.beautifyString( page.getName() ) + " " + unTokenizedTitle, |
| TextField.TYPE_STORED ); |
| doc.add( field ); |
| |
| // Allow searching by authorname |
| |
| if( page.getAuthor() != null ) |
| { |
| field = new Field( LUCENE_AUTHOR, page.getAuthor(), TextField.TYPE_STORED ); |
| doc.add( field ); |
| } |
| |
| // Now add the names of the attachments of this page |
| try |
| { |
| Collection attachments = m_engine.getAttachmentManager().listAttachments(page); |
| String attachmentNames = ""; |
| |
| for( Iterator it = attachments.iterator(); it.hasNext(); ) |
| { |
| Attachment att = (Attachment) it.next(); |
| attachmentNames += att.getName() + ";"; |
| } |
| field = new Field( LUCENE_ATTACHMENTS, attachmentNames, TextField.TYPE_STORED ); |
| doc.add( field ); |
| |
| } |
| catch(ProviderException e) |
| { |
| // Unable to read attachments |
| log.error("Failed to get attachments for page", e); |
| } |
| writer.addDocument(doc); |
| |
| return doc; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void pageRemoved( WikiPage page ) |
| { |
| IndexWriter writer = null; |
| try |
| { |
| Directory luceneDir = new SimpleFSDirectory(new File(m_luceneDirectory), null); |
| writer = getIndexWriter( luceneDir ); |
| Query query = new TermQuery( new Term( LUCENE_ID, page.getName() ) ); |
| writer.deleteDocuments( query ); |
| } |
| catch ( Exception e ) |
| { |
| log.error("Unable to remove page '" + page.getName() + "' from Lucene index", e); |
| } |
| finally |
| { |
| close( writer ); |
| } |
| } |
| |
| IndexWriter getIndexWriter( Directory luceneDir ) throws CorruptIndexException, |
| LockObtainFailedException, IOException, ProviderException |
| { |
| IndexWriter writer = null; |
| IndexWriterConfig writerConfig = new IndexWriterConfig( Version.LUCENE_47, getLuceneAnalyzer() ); |
| writerConfig.setOpenMode( OpenMode.CREATE_OR_APPEND ); |
| writer = new IndexWriter( luceneDir, writerConfig ); |
| |
| // writer.setInfoStream( System.out ); |
| return writer; |
| } |
| |
| void close( IndexWriter writer ) |
| { |
| try |
| { |
| if( writer != null ) |
| { |
| writer.close( true ); |
| } |
| } |
| catch( IOException e ) |
| { |
| log.error( e ); |
| } |
| } |
| |
| |
| /** |
| * Adds a page-text pair to the lucene update queue. Safe to call always |
| * |
| * @param page WikiPage to add to the update queue. |
| */ |
| public void reindexPage( WikiPage page ) |
| { |
| if( page != null ) |
| { |
| String text; |
| |
| // TODO: Think if this was better done in the thread itself? |
| |
| if( page instanceof Attachment ) |
| { |
| text = getAttachmentContent( (Attachment) page ); |
| } |
| else |
| { |
| text = m_engine.getPureText( page ); |
| } |
| |
| if( text != null ) |
| { |
| // Add work item to m_updates queue. |
| Object[] pair = new Object[2]; |
| pair[0] = page; |
| pair[1] = text; |
| m_updates.add(pair); |
| log.debug("Scheduling page " + page.getName() + " for index update"); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Collection findPages( String query, WikiContext wikiContext ) |
| throws ProviderException |
| { |
| return findPages( query, FLAG_CONTEXTS, wikiContext ); |
| } |
| |
| /** |
| * Create contexts also. Generating contexts can be expensive, |
| * so they're not on by default. |
| */ |
| public static final int FLAG_CONTEXTS = 0x01; |
| |
| /** |
| * Searches pages using a particular combination of flags. |
| * |
| * @param query The query to perform in Lucene query language |
| * @param flags A set of flags |
| * @return A Collection of SearchResult instances |
| * @throws ProviderException if there is a problem with the backend |
| */ |
| public Collection findPages( String query, int flags, WikiContext wikiContext ) |
| throws ProviderException |
| { |
| IndexSearcher searcher = null; |
| ArrayList<SearchResult> list = null; |
| Highlighter highlighter = null; |
| |
| try |
| { |
| String[] queryfields = { LUCENE_PAGE_CONTENTS, LUCENE_PAGE_NAME, LUCENE_AUTHOR, LUCENE_ATTACHMENTS }; |
| QueryParser qp = new MultiFieldQueryParser( Version.LUCENE_47, queryfields, getLuceneAnalyzer() ); |
| |
| //QueryParser qp = new QueryParser( LUCENE_PAGE_CONTENTS, getLuceneAnalyzer() ); |
| Query luceneQuery = qp.parse( query ); |
| |
| if( (flags & FLAG_CONTEXTS) != 0 ) |
| { |
| highlighter = new Highlighter(new SimpleHTMLFormatter("<span class=\"searchmatch\">", "</span>"), |
| new SimpleHTMLEncoder(), |
| new QueryScorer(luceneQuery)); |
| } |
| |
| try |
| { |
| File dir = new File(m_luceneDirectory); |
| Directory luceneDir = new SimpleFSDirectory(dir, null); |
| IndexReader reader = DirectoryReader.open(luceneDir); |
| searcher = new IndexSearcher(reader); |
| } |
| catch( Exception ex ) |
| { |
| log.info("Lucene not yet ready; indexing not started",ex); |
| return null; |
| } |
| |
| ScoreDoc[] hits = searcher.search(luceneQuery, MAX_SEARCH_HITS).scoreDocs; |
| |
| AuthorizationManager mgr = m_engine.getAuthorizationManager(); |
| |
| list = new ArrayList<SearchResult>(hits.length); |
| for ( int curr = 0; curr < hits.length; curr++ ) |
| { |
| int docID = hits[curr].doc; |
| Document doc = searcher.doc( docID ); |
| String pageName = doc.get(LUCENE_ID); |
| WikiPage page = m_engine.getPage(pageName, WikiPageProvider.LATEST_VERSION); |
| |
| if(page != null) |
| { |
| if(page instanceof Attachment) |
| { |
| // Currently attachments don't look nice on the search-results page |
| // When the search-results are cleaned up this can be enabled again. |
| } |
| |
| PagePermission pp = new PagePermission( page, PagePermission.VIEW_ACTION ); |
| if( mgr.checkPermission( wikiContext.getWikiSession(), pp ) ) { |
| |
| int score = (int)(hits[curr].score * 100); |
| |
| |
| // Get highlighted search contexts |
| String text = doc.get(LUCENE_PAGE_CONTENTS); |
| |
| String[] fragments = new String[0]; |
| if( text != null && highlighter != null ) { |
| TokenStream tokenStream = getLuceneAnalyzer() |
| .tokenStream(LUCENE_PAGE_CONTENTS, new StringReader(text)); |
| fragments = highlighter.getBestFragments(tokenStream, text, MAX_FRAGMENTS); |
| } |
| |
| SearchResult result = new SearchResultImpl( page, score, fragments ); |
| list.add(result); |
| } |
| } |
| else |
| { |
| log.error("Lucene found a result page '" + pageName + "' that could not be loaded, removing from Lucene cache"); |
| pageRemoved(new WikiPage( m_engine, pageName )); |
| } |
| } |
| } |
| catch( IOException e ) |
| { |
| log.error("Failed during lucene search",e); |
| } |
| catch( ParseException e ) |
| { |
| log.info("Broken query; cannot parse query ",e); |
| |
| throw new ProviderException("You have entered a query Lucene cannot process: "+e.getMessage()); |
| } |
| catch( InvalidTokenOffsetsException e ) |
| { |
| log.error("Tokens are incompatible with provided text ",e); |
| } |
| finally |
| { |
| if( searcher != null ) |
| { |
| try |
| { |
| searcher.getIndexReader().close(); |
| } |
| catch( IOException e ) |
| { |
| log.error( e ); |
| } |
| } |
| } |
| |
| return list; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getProviderInfo() |
| { |
| return "LuceneSearchProvider"; |
| } |
| |
| /** |
| * Updater thread that updates Lucene indexes. |
| */ |
| private static final class LuceneUpdater extends WikiBackgroundThread |
| { |
| protected static final int INDEX_DELAY = 5; |
| protected static final int INITIAL_DELAY = 60; |
| private final LuceneSearchProvider m_provider; |
| |
| private int m_initialDelay; |
| |
| private WatchDog m_watchdog; |
| |
| private LuceneUpdater( WikiEngine engine, LuceneSearchProvider provider, |
| int initialDelay, int indexDelay ) |
| { |
| super( engine, indexDelay ); |
| m_provider = provider; |
| setName("JSPWiki Lucene Indexer"); |
| } |
| |
| public void startupTask() throws Exception |
| { |
| m_watchdog = getEngine().getCurrentWatchDog(); |
| |
| // Sleep initially... |
| try |
| { |
| Thread.sleep( m_initialDelay * 1000L ); |
| } |
| catch( InterruptedException e ) |
| { |
| throw new InternalWikiException("Interrupted while waiting to start."); |
| } |
| |
| m_watchdog.enterState("Full reindex"); |
| // Reindex everything |
| m_provider.doFullLuceneReindex(); |
| m_watchdog.exitState(); |
| } |
| |
| public void backgroundTask() throws Exception |
| { |
| m_watchdog.enterState("Emptying index queue", 60); |
| |
| synchronized ( m_provider.m_updates ) |
| { |
| while( m_provider.m_updates.size() > 0 ) |
| { |
| Object[] pair = m_provider.m_updates.remove(0); |
| WikiPage page = ( WikiPage ) pair[0]; |
| String text = ( String ) pair[1]; |
| m_provider.updateLuceneIndex(page, text); |
| } |
| } |
| |
| m_watchdog.exitState(); |
| } |
| |
| } |
| |
| // FIXME: This class is dumb; needs to have a better implementation |
| private static class SearchResultImpl |
| implements SearchResult |
| { |
| private WikiPage m_page; |
| private int m_score; |
| private String[] m_contexts; |
| |
| public SearchResultImpl( WikiPage page, int score, String[] contexts ) |
| { |
| m_page = page; |
| m_score = score; |
| m_contexts = contexts != null ? contexts.clone() : null; |
| } |
| |
| public WikiPage getPage() |
| { |
| return m_page; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.SearchResult#getScore() |
| */ |
| public int getScore() |
| { |
| return m_score; |
| } |
| |
| |
| public String[] getContexts() |
| { |
| return m_contexts; |
| } |
| } |
| } |