/*
* 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.
*/
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Xml;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Store;
using Lucene.Net.Distributed;
namespace Lucene.Net.Distributed.Configuration
{
public enum IndexSetting
{
NoSetting = 0,
IndexA = 1,
IndexB = 2
}
///
/// Definition of current index information managed by the
/// LuceneUpdater windows service. The node within the
/// node represents the information needed to load
/// a CurrentIndex object for a given IndexSet.
///
/// An example configuration would look like the following:
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
public class CurrentIndex
{
#region Variables
private static readonly string CURRENTINDEX = "currentIndex";
private static readonly string INDEX_A = "indexA";
private static readonly string INDEX_B = "indexB";
private static readonly string TOGGLE = "toggle";
private string _strLocalPath;
private string _strStatusDir;
private string _strIndexAPath;
private string _strIndexBPath;
private bool _bIndexChanged=false;
private int _mergeFactor = (ConfigurationManager.AppSettings["IndexMergeFactor"] != null ? Convert.ToInt32(ConfigurationManager.AppSettings["IndexMergeFactor"]) : 5);
private int _maxMergeDocs = (ConfigurationManager.AppSettings["IndexMaxMergeDocs"] != null ? Convert.ToInt32(ConfigurationManager.AppSettings["IndexMaxMergeDocs"]) : 9999999);
#endregion
#region Constructors
///
/// Constructs a new CurrentIndex using the XmlNode value (from IndexSetConfigurationHandler configuration)
///
/// XmlNode containing configuration information
/// Local filesystem path to source index
public CurrentIndex(XmlNode node, string strLocalPath)
{
this._strLocalPath=strLocalPath;
this.LoadValues(node);
}
///
/// Constructs a shell CurrentIndex. Use this constructor to interact
/// with the underlying status and toggle files ONLY.
///
/// Filesystem path to the status and toggle files for an index
public CurrentIndex(string sStatusDir)
{
this._strStatusDir=sStatusDir;
}
#endregion
#region Internals
///
/// Internal routine for use by constructor that accepts a configuration
/// entry structured as XmlNode.
///
/// XmlNode containing configuration information
internal void LoadValues(XmlNode node)
{
foreach (XmlNode c in node.ChildNodes)
{
if (c.Name.ToLower()=="targetpath")
{
this._strIndexAPath = c.Attributes["indexA"].Value;
this._strIndexBPath = c.Attributes["indexB"].Value;
}
else if (c.Name.ToLower()=="statusdir")
{
this._strStatusDir = c.Attributes["value"].Value;
}
}
this.CheckValidConfiguration(node);
}
#endregion
#region Public properties
///
/// Filesystem path to the local source for an index; this is the path to the master index.
///
public string LocalPath
{
get {return this._strLocalPath;}
}
///
/// Filesystem path to a LuceneServer's status and toggle file for a given IndexSet
///
public string StatusDirectory
{
get {return this._strStatusDir;}
}
///
/// Indicates the current index directory (IndexSetting enum) in use (the online set)
///
public IndexSetting IndexSetting
{
get
{
string input=(this.GetCurrentIndex());
return (input==CurrentIndex.INDEX_A ? IndexSetting.IndexA : (input==CurrentIndex.INDEX_B ? IndexSetting.IndexB : IndexSetting.IndexA));
}
}
///
/// Indicates the index directory to be used in any index searcher refresh
/// by determining if any updates have been applied
///
public IndexSetting IndexSettingRefresh
{
get
{
if (this.HasChanged)
{
return (this.IndexSetting==IndexSetting.IndexA ? IndexSetting.IndexB : (this.IndexSetting==IndexSetting.IndexB ? IndexSetting.IndexA : IndexSetting.IndexB ));
}
else
{
return this.IndexSetting;
}
}
}
///
/// Indicates if the current index permits updated indexes to be copied to CopyTargetPath
///
public bool CanCopy
{
get {return (!this.GetToggle() && (this.LocalIndexVersion!=this.TargetIndexVersion));}
}
///
/// Indicates if the current index has pending updates (in the offline directory) to be used by an index searcher
/// in a refresh evaluation
///
public bool HasChanged
{
get {return this.GetToggle();}
}
///
/// The target directory path to be used when updating the offline index
///
public string CopyTargetPath
{
get {return (this.IndexSetting==IndexSetting.IndexA ? this._strIndexBPath : (this.IndexSetting==IndexSetting.IndexB ? this._strIndexAPath : ""));}
}
#endregion
#region Public methods
///
/// Method that executes a filesystem copy of all directory files from a local path to
/// the proper offline index. This method ensures no conflicts occur with the online index.
///
/// bool
public bool Copy()
{
try
{
if (this.CanCopy && this.CopyTargetPath!="")
{
this.DeleteDirectoryFiles(this.CopyTargetPath);
this.CopyDirectory(this._strLocalPath, this.CopyTargetPath);
return true;
}
else
{
return false;
}
}
catch (Exception e)
{
//Do something with e
return false;
}
}
///
/// Method that executes a filesystem copy of updated or new files from a local path to
/// the proper offline index. This method ensures no conflicts occur with the online index.
///
///
public bool CopyIncremental()
{
try
{
if (this.CanCopy && this.CopyTargetPath!="")
{
this.CopyDirectoryIncremental(this._strLocalPath, this.CopyTargetPath);
return true;
}
else
{
return false;
}
}
catch (Exception e)
{
//Do something with e
return false;
}
}
///
/// Takes a name/value pair collection to be used in updating an index.
/// Deletes are necessary to ensure no duplication occurs within the index.
///
/// Set of record IDs (with underlying field name) to be applied for index updating
public void ProcessLocalIndexDeletes(NameValueCollection nvcDeleteCollection)
{
if (IndexReader.IndexExists(this._strLocalPath) && nvcDeleteCollection.Count>0)
{
IndexReader idxDeleter = IndexReader.Open(this._strLocalPath);
string[] arKeys = nvcDeleteCollection.AllKeys;
int xDelete=0;
for (int k=0;k
/// Executes a loop on the Documents arraylist, adding each one to the index with the associated analyzer.
///
/// Analyzer to be used in index document addition
/// Arraylist of Lucene Document objects to be inserted in the index
/// Setting to dictate if the index should use compound format
public void ProcessLocalIndexAdditions(Analyzer oAnalyzer, Hashtable htAddDocuments, bool bCompoundFile)
{
IndexWriter idxWriter = this.GetIndexWriter(this._strLocalPath, oAnalyzer, bCompoundFile);
idxWriter.SetMergeFactor(5);
idxWriter.SetMaxMergeDocs(9999999);
foreach (DictionaryEntry de in htAddDocuments)
{
Document d = (Document)de.Key;
Analyzer a = (Analyzer)de.Value;
idxWriter.AddDocument(d,a);
}
idxWriter.Close();
}
///
/// Single method to be used by a searchhost to indicate an index refresh has completed.
///
public void IndexRefresh()
{
if (this.HasChanged)
{
this.SetCurrentIndex(this.IndexSettingRefresh);
this.SetToggle(false);
}
}
///
/// Single method to be used by an index updater to indicate an index update has completed.
///
public void UpdateRefresh()
{
this.SetToggle(true);
}
#endregion
#region Private properties
///
/// The filesystem path to the underlying index status file
///
private string CurrentIndexFile
{
get {return (this._strStatusDir+(this._strStatusDir.EndsWith(@"\") ? "" : @"\")+CURRENTINDEX);}
}
///
/// The filesystem path to the underlying index toggle file
///
private string ToggleFile
{
get {return (this._strStatusDir+(this._strStatusDir.EndsWith(@"\") ? "" : @"\")+TOGGLE);}
}
#endregion
#region Private methods
///
/// Validation routine to ensure all required values were present within xml configuration node
/// used in constructor.
///
/// XmlNode containing configuration information
private void CheckValidConfiguration(XmlNode node)
{
if (this._strLocalPath == null) throw new ConfigurationErrorsException("CurrentIndex local path invalid: "+Environment.NewLine+node.OuterXml);
if (this._strStatusDir == null) throw new ConfigurationErrorsException("CurrentIndex statusDir invalid: " + Environment.NewLine + node.OuterXml);
if (this._strIndexAPath == null) throw new ConfigurationErrorsException("CurrentIndex indexA invalid: " + Environment.NewLine + node.OuterXml);
if (this._strIndexBPath == null) throw new ConfigurationErrorsException("CurrentIndex indexB invalid: " + Environment.NewLine + node.OuterXml);
}
///
/// Returns the current toggle file setting
///
/// bool
private bool GetToggle()
{
bool bValue=false;
string input="";
try
{
if (!File.Exists(this.ToggleFile))
{
this.SetToggle(false);
}
else
{
StreamReader sr = File.OpenText(this.ToggleFile);
input = sr.ReadLine();
sr.Close();
bValue = (input.ToLower()=="true" ? true : false);
}
}
catch (Exception ex)
{
//Do something with ex
}
return bValue;
}
///
/// Returns the current status file setting
///
/// string
private string GetCurrentIndex()
{
string input="";
try
{
if (!File.Exists(this.CurrentIndexFile))
{
this.SetCurrentIndex(IndexSetting.IndexA);
input=IndexSetting.IndexA.ToString();
}
else
{
StreamReader sr = File.OpenText(this.CurrentIndexFile);
input = sr.ReadLine();
sr.Close();
}
}
catch (Exception ex)
{
//Do something with ex
}
return input;
}
///
/// Updates the status file with the IndexSetting value parameter
///
/// Setting to be applied to the status file
private void SetCurrentIndex(IndexSetting eIndexSetting)
{
try
{
StreamWriter sw = File.CreateText(this.CurrentIndexFile);
sw.WriteLine((eIndexSetting==IndexSetting.IndexA ? CurrentIndex.INDEX_A : CurrentIndex.INDEX_B));
sw.Close();
}
catch (Exception ex)
{
//Do something with ex
}
}
///
/// IndexWriter that can be used to apply updates to an index
///
/// File system path to the target index
/// Lucene Analyzer to be used by the underlying IndexWriter
/// Setting to dictate if the index should use compound format
///
private IndexWriter GetIndexWriter(string indexPath, Analyzer oAnalyzer, bool bCompoundFile)
{
bool bExists = System.IO.Directory.Exists(indexPath);
if (bExists==false)
System.IO.Directory.CreateDirectory(indexPath);
bExists=IndexReader.IndexExists(FSDirectory.GetDirectory(indexPath, false));
IndexWriter idxWriter = new IndexWriter(indexPath, oAnalyzer, !bExists);
idxWriter.SetUseCompoundFile(bCompoundFile);
return idxWriter;
}
///
/// Updates the toggle file with the bool value parameter
///
/// Bool to be applied to the toggle file
private void SetToggle(bool bValue)
{
try
{
StreamWriter sw = File.CreateText(this.ToggleFile);
sw.WriteLine(bValue.ToString());
sw.Close();
this._bIndexChanged=bValue;
}
catch (Exception ex)
{
//Do something with ex
}
}
///
/// Returns the numeric index version (using Lucene objects) for the index located at LocalPath
///
private long LocalIndexVersion
{
get {return IndexReader.GetCurrentVersion(this.LocalPath);}
}
///
/// Returns the numeric index version (using Lucene objects) for the index located at CopyTargetPath
///
private long TargetIndexVersion
{
get {return (IndexReader.IndexExists(this.CopyTargetPath) ? IndexReader.GetCurrentVersion(this.CopyTargetPath) : 0);}
}
///
/// Deletes index files at the filesystem directoryPath location
///
/// Filesystem path
private void DeleteDirectoryFiles(string directoryPath)
{
try
{
if(!System.IO.Directory.Exists(directoryPath))
return;
DirectoryInfo di = new DirectoryInfo(directoryPath);
FileInfo[] arFi = di.GetFiles();
foreach(FileInfo fi in arFi)
fi.Delete();
}
catch(Exception e)
{
//Do something with e
}
}
///
/// Copy all index files from the sourceDirPath to the destDirPath
///
/// Filesystem path
/// Filesystem path
private void CopyDirectory(string sourceDirPath, string destDirPath)
{
string[] Files;
if(destDirPath[destDirPath.Length-1]!=Path.DirectorySeparatorChar)
destDirPath+=Path.DirectorySeparatorChar;
if(!System.IO.Directory.Exists(destDirPath)) System.IO.Directory.CreateDirectory(destDirPath);
Files=System.IO.Directory.GetFileSystemEntries(sourceDirPath);
foreach(string Element in Files)
{
// Sub directories
if(System.IO.Directory.Exists(Element))
CopyDirectory(Element,destDirPath+Path.GetFileName(Element));
// Files in directory
else
File.Copy(Element,destDirPath+Path.GetFileName(Element),true);
}
}
///
/// Copy only new and updated index files from the sourceDirPath to the destDirPath
///
/// Filesystem path
/// Filesystem path
private void CopyDirectoryIncremental(string sourceDirPath, string destDirPath)
{
string[] Files;
if(destDirPath[destDirPath.Length-1]!=Path.DirectorySeparatorChar)
destDirPath+=Path.DirectorySeparatorChar;
Files=System.IO.Directory.GetFileSystemEntries(sourceDirPath);
if(!System.IO.Directory.Exists(destDirPath))
{
System.IO.Directory.CreateDirectory(destDirPath);
foreach(string Element in Files)
{
// Sub directories
if(System.IO.Directory.Exists(Element))
CopyDirectory(Element,destDirPath+Path.GetFileName(Element));
// Files in directory
else
File.Copy(Element,destDirPath+Path.GetFileName(Element),true);
}
}
else
{
foreach(string Element in Files)
{
if(System.IO.Directory.Exists(Element))
{
CopyDirectoryIncremental(Element,destDirPath+Path.GetFileName(Element));
}
else
{
if (System.IO.File.Exists(destDirPath+Path.GetFileName(Element)))
this.CopyFileIncremental(Element, destDirPath+Path.GetFileName(Element));
else
File.Copy(Element,destDirPath+Path.GetFileName(Element),true);
}
}
}
}
///
/// Evaluates the LastWriteTime and Length properties of two files to determine
/// if a file should be copied.
///
/// Filesystem path
/// Filesystem path
private void CopyFileIncremental(string filepath1, string filepath2)
{
FileInfo fi1 = new FileInfo(filepath1);
FileInfo fi2 = new FileInfo(filepath2);
if ((fi1.LastWriteTime!=fi2.LastWriteTime)||(fi1.Length!=fi2.Length))
File.Copy(filepath1,filepath2,true);
}
#endregion
#region Static methods
///
/// Returns an Analyzer for the given AnalyzerType
///
/// Enumeration value
/// Analyzer
public static Analyzer GetAnalyzer(AnalyzerType oAnalyzerType)
{
Analyzer oAnalyzer = null;
switch (oAnalyzerType)
{
case AnalyzerType.SimpleAnalyzer:
oAnalyzer = new SimpleAnalyzer();
break;
case AnalyzerType.StopAnalyzer:
oAnalyzer = new StopAnalyzer();
break;
case AnalyzerType.WhitespaceAnalyzer:
oAnalyzer = new WhitespaceAnalyzer();
break;
default:
case AnalyzerType.StandardAnalyzer:
oAnalyzer = new StandardAnalyzer();
break;
}
return oAnalyzer;
}
#endregion
}
}