You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by ni...@apache.org on 2017/08/18 08:04:50 UTC
[03/20] lucenenet git commit: LUCENENET-565: Porting of Lucene
Replicator - Commit is for Review with comments about original Java Source
for assistance.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Replicator/Lucene.Net.Replicator.csproj
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Replicator/Lucene.Net.Replicator.csproj b/src/Lucene.Net.Replicator/Lucene.Net.Replicator.csproj
new file mode 100644
index 0000000..9481bd4
--- /dev/null
+++ b/src/Lucene.Net.Replicator/Lucene.Net.Replicator.csproj
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+ 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.
+
+-->
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{1F70D2DB-C1B3-4F78-9598-3E04E0C7EB06}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Lucene.Net.Replicator</RootNamespace>
+ <AssemblyName>Lucene.Net.Replicator</AssemblyName>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ComponentWrapperInfoStream.cs" />
+ <Compile Include="Http\EnumerableExtensions.cs" />
+ <Compile Include="Http\HttpClientBase.cs" />
+ <Compile Include="Http\HttpReplicator.cs" />
+ <Compile Include="Http\Abstractions\IReplicationRequest.cs" />
+ <Compile Include="Http\Abstractions\IReplicationResponse.cs" />
+ <Compile Include="Http\ReplicationService.cs" />
+ <Compile Include="IndexAndTaxonomyReplicationHandler.cs" />
+ <Compile Include="IndexAndTaxonomyRevision.cs" />
+ <Compile Include="IndexInputInputStream.cs" />
+ <Compile Include="IndexReplicationHandler.cs" />
+ <Compile Include="IndexRevision.cs" />
+ <Compile Include="IReplicationHandler.cs" />
+ <Compile Include="ISourceDirectoryFactory.cs" />
+ <Compile Include="LocalReplicator.cs" />
+ <Compile Include="PerSessionDirectoryFactory.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="ReplicationClient.cs" />
+ <Compile Include="Replicator.cs" />
+ <Compile Include="Revision.cs" />
+ <Compile Include="RevisionFile.cs" />
+ <Compile Include="SessionExpiredException.cs" />
+ <Compile Include="SessionToken.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="Http\package.html" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Lucene.Net.Facet\Lucene.Net.Facet.csproj">
+ <Project>{48f7884a-9454-4e88-8413-9d35992cb440}</Project>
+ <Name>Lucene.Net.Facet</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Lucene.Net\Lucene.Net.csproj">
+ <Project>{5d4ad9be-1ffb-41ab-9943-25737971bf57}</Project>
+ <Name>Lucene.Net</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Replicator/PerSessionDirectoryFactory.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Replicator/PerSessionDirectoryFactory.cs b/src/Lucene.Net.Replicator/PerSessionDirectoryFactory.cs
new file mode 100644
index 0000000..e7f1d80
--- /dev/null
+++ b/src/Lucene.Net.Replicator/PerSessionDirectoryFactory.cs
@@ -0,0 +1,96 @@
+//STATUS: DRAFT - 4.8.0
+
+using System;
+using System.IO;
+using Lucene.Net.Store;
+using Directory = Lucene.Net.Store.Directory;
+
+namespace Lucene.Net.Replicator
+{
+ /*
+ * 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.
+ */
+
+ /// <summary>
+ /// A <see cref="ISourceDirectoryFactory"/> which returns <see cref="FSDirectory"/> under a
+ /// dedicated session directory. When a session is over, the entire directory is
+ /// deleted.
+ /// </summary>
+ /// <remarks>
+ /// Lucene.Experimental
+ /// </remarks>
+ public class PerSessionDirectoryFactory : ISourceDirectoryFactory
+ {
+ #region Java
+ //JAVA: private final File workDir;
+ #endregion
+ private readonly string workingDirectory;
+
+ /** Constructor with the given sources mapping. */
+ public PerSessionDirectoryFactory(string workingDirectory)
+ {
+ this.workingDirectory = workingDirectory;
+ }
+
+ public Directory GetDirectory(string sessionId, string source)
+ {
+ #region Java
+ //JAVA: public Directory getDirectory(String sessionID, String source) throws IOException {
+ //JAVA: File sessionDir = new File(workDir, sessionID);
+ //JAVA: if (!sessionDir.exists() && !sessionDir.mkdirs()) {
+ //JAVA: throw new IOException("failed to create session directory " + sessionDir);
+ //JAVA: }
+ //JAVA: File sourceDir = new File(sessionDir, source);
+ //JAVA: if (!sourceDir.mkdirs()) {
+ //JAVA: throw new IOException("failed to create source directory " + sourceDir);
+ //JAVA: }
+ //JAVA: return FSDirectory.open(sourceDir);
+ //JAVA: }
+ #endregion
+
+ string sourceDirectory = Path.Combine(workingDirectory, sessionId, source);
+ System.IO.Directory.CreateDirectory(sourceDirectory);
+ return FSDirectory.Open(sourceDirectory);
+ }
+
+ public void CleanupSession(string sessionId)
+ {
+ if (string.IsNullOrEmpty(sessionId)) throw new ArgumentException("sessionID cannot be empty", "sessionId");
+
+ #region Java
+ //JAVA: rm(new File(workDir, sessionID));
+ #endregion
+
+ string sessionDirectory = Path.Combine(workingDirectory, sessionId);
+ System.IO.Directory.Delete(sessionDirectory, true);
+ }
+
+ #region Java
+ //JAVA: private void rm(File file) throws IOException {
+ //JAVA: if (file.isDirectory()) {
+ //JAVA: for (File f : file.listFiles()) {
+ //JAVA: rm(f);
+ //JAVA: }
+ //JAVA: }
+ //JAVA:
+ //JAVA: // This should be either an empty directory, or a file
+ //JAVA: if (!file.delete() && file.exists()) {
+ //JAVA: throw new IOException("failed to delete " + file);
+ //JAVA: }
+ //JAVA: }
+ #endregion
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Replicator/Properties/AssemblyInfo.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Replicator/Properties/AssemblyInfo.cs b/src/Lucene.Net.Replicator/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..898ca18
--- /dev/null
+++ b/src/Lucene.Net.Replicator/Properties/AssemblyInfo.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Lucene.Net.Replicator")]
+[assembly: AssemblyDescription("Replicator that allows replication of Lucene.Net files between a server and client(s) " +
+ "for the Lucene.Net full - text search engine library from The Apache Software Foundation.")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyDefaultAlias("Lucene.Net.Replicator")]
+[assembly: AssemblyCulture("")]
+
+[assembly: CLSCompliant(true)]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("1f70d2db-c1b3-4f78-9598-3e04e0c7eb06")]
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Replicator/ReplicationClient.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Replicator/ReplicationClient.cs b/src/Lucene.Net.Replicator/ReplicationClient.cs
new file mode 100644
index 0000000..63837c9
--- /dev/null
+++ b/src/Lucene.Net.Replicator/ReplicationClient.cs
@@ -0,0 +1,673 @@
+//STATUS: DRAFT - 4.8.0
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using Lucene.Net.Store;
+using Lucene.Net.Support;
+using Lucene.Net.Support.Threading;
+using Lucene.Net.Util;
+using Directory = Lucene.Net.Store.Directory;
+
+namespace Lucene.Net.Replicator
+{
+ /*
+ * 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.
+ */
+
+ /// <summary>
+ /// A client which monitors and obtains new revisions from a <see cref="IReplicator"/>.
+ /// It can be used to either periodically check for updates by invoking
+ /// <see cref="StartUpdateThread"/>, or manually by calling <see cref="UpdateNow"/>.
+ /// <para>
+ /// Whenever a new revision is available, the <see cref="RequiredFiles"/> are
+ /// copied to the <see cref="Directory"/> specified by <see cref="PerSessionDirectoryFactory"/> and
+ /// a handler is notified.
+ /// </para>
+ /// </summary>
+ /// <remarks>
+ /// Lucene.Experimental
+ /// </remarks>
+ public partial class ReplicationClient : IDisposable
+ {
+ /// <summary>
+ /// The component name to use with <see cref="Util.InfoStream.IsEnabled"/>
+ /// </summary>
+ public const string INFO_STREAM_COMPONENT = "ReplicationThread";
+
+ /// <summary> Gets or sets the <see cref="Util.InfoStream"/> to use for logging messages. </summary>
+ public InfoStream InfoStream
+ {
+ get { return infoStream; }
+ set { infoStream = value ?? InfoStream.NO_OUTPUT; }
+ }
+
+ private readonly IReplicator replicator;
+ private readonly IReplicationHandler handler;
+ private readonly ISourceDirectoryFactory factory;
+
+ private readonly byte[] copyBuffer = new byte[16384];
+ private readonly ReentrantLock updateLock = new ReentrantLock();
+
+ private ReplicationThread updateThread;
+ private bool disposed = false;
+ private InfoStream infoStream = InfoStream.Default;
+
+ /// <summary>
+ /// Constructor.
+ /// </summary>
+ /// <param name="replicator">The <see cref="IReplicator"/> used for checking for updates</param>
+ /// <param name="handler">The <see cref="IReplicationHandler"/> notified when new revisions are ready</param>
+ /// <param name="factory">The <see cref="ISourceDirectoryFactory"/> for returning a <see cref="Directory"/> for a given source and session</param>
+ public ReplicationClient(IReplicator replicator, IReplicationHandler handler, ISourceDirectoryFactory factory)
+ {
+ this.replicator = replicator;
+ this.handler = handler;
+ this.factory = factory;
+ }
+
+ /// <exception cref="IOException"></exception>
+ private void DoUpdate()
+ {
+ #region Java
+ //JAVA: private void doUpdate() throws IOException {
+ //JAVA: SessionToken session = null;
+ //JAVA: final Map<String,Directory> sourceDirectory = new HashMap<>();
+ //JAVA: final Map<String,List<String>> copiedFiles = new HashMap<>();
+ //JAVA: boolean notify = false;
+ //JAVA: try {
+ //JAVA: final String version = handler.currentVersion();
+ //JAVA: session = replicator.checkForUpdate(version);
+ //JAVA: if (infoStream.isEnabled(INFO_STREAM_COMPONENT)) {
+ //JAVA: infoStream.message(INFO_STREAM_COMPONENT, "doUpdate(): handlerVersion=" + version + " session=" + session);
+ //JAVA: }
+ //JAVA: if (session == null) {
+ //JAVA: // already up to date
+ //JAVA: return;
+ //JAVA: }
+ //JAVA: Map<String,List<RevisionFile>> requiredFiles = requiredFiles(session.sourceFiles);
+ //JAVA: if (infoStream.isEnabled(INFO_STREAM_COMPONENT)) {
+ //JAVA: infoStream.message(INFO_STREAM_COMPONENT, "doUpdate(): requiredFiles=" + requiredFiles);
+ //JAVA: }
+ //JAVA: for (Entry<String,List<RevisionFile>> e : requiredFiles.entrySet()) {
+ //JAVA: String source = e.getKey();
+ //JAVA: Directory dir = factory.getDirectory(session.id, source);
+ //JAVA: sourceDirectory.put(source, dir);
+ //JAVA: List<String> cpFiles = new ArrayList<>();
+ //JAVA: copiedFiles.put(source, cpFiles);
+ //JAVA: for (RevisionFile file : e.getValue()) {
+ //JAVA: if (closed) {
+ //JAVA: // if we're closed, abort file copy
+ //JAVA: if (infoStream.isEnabled(INFO_STREAM_COMPONENT)) {
+ //JAVA: infoStream.message(INFO_STREAM_COMPONENT, "doUpdate(): detected client was closed); abort file copy");
+ //JAVA: }
+ //JAVA: return;
+ //JAVA: }
+ //JAVA: InputStream in = null;
+ //JAVA: IndexOutput out = null;
+ //JAVA: try {
+ //JAVA: in = replicator.obtainFile(session.id, source, file.fileName);
+ //JAVA: out = dir.createOutput(file.fileName, IOContext.DEFAULT);
+ //JAVA: copyBytes(out, in);
+ //JAVA: cpFiles.add(file.fileName);
+ //JAVA: // TODO add some validation, on size / checksum
+ //JAVA: } finally {
+ //JAVA: IOUtils.close(in, out);
+ //JAVA: }
+ //JAVA: }
+ //JAVA: }
+ //JAVA: // only notify if all required files were successfully obtained.
+ //JAVA: notify = true;
+ //JAVA: } finally {
+ //JAVA: if (session != null) {
+ //JAVA: try {
+ //JAVA: replicator.release(session.id);
+ //JAVA: } finally {
+ //JAVA: if (!notify) { // cleanup after ourselves
+ //JAVA: IOUtils.close(sourceDirectory.values());
+ //JAVA: factory.cleanupSession(session.id);
+ //JAVA: }
+ //JAVA: }
+ //JAVA: }
+ //JAVA: }
+ //JAVA:
+ //JAVA: // notify outside the try-finally above, so the session is released sooner.
+ //JAVA: // the handler may take time to finish acting on the copied files, but the
+ //JAVA: // session itself is no longer needed.
+ //JAVA: try {
+ //JAVA: if (notify && !closed ) { // no use to notify if we are closed already
+ //JAVA: handler.revisionReady(session.version, session.sourceFiles, copiedFiles, sourceDirectory);
+ //JAVA: }
+ //JAVA: } finally {
+ //JAVA: IOUtils.close(sourceDirectory.values());
+ //JAVA: if (session != null) {
+ //JAVA: factory.cleanupSession(session.id);
+ //JAVA: }
+ //JAVA: }
+ //JAVA: }
+ #endregion
+
+ SessionToken session = null;
+ Dictionary<string, Directory> sourceDirectory = new Dictionary<string, Directory>();
+ Dictionary<string, IList<string>> copiedFiles = new Dictionary<string, IList<string>>();
+ bool notify = false;
+ try
+ {
+ string version = handler.CurrentVersion;
+ session = replicator.CheckForUpdate(version);
+
+ WriteToInfoStream(string.Format("doUpdate(): handlerVersion={0} session={1}", version, session));
+
+ if (session == null)
+ return;
+
+ IDictionary<string, IList<RevisionFile>> requiredFiles = RequiredFiles(session.SourceFiles);
+ WriteToInfoStream(string.Format("doUpdate(): handlerVersion={0} session={1}", version, session));
+
+ foreach (KeyValuePair<string, IList<RevisionFile>> pair in requiredFiles)
+ {
+ string source = pair.Key;
+ Directory directory = factory.GetDirectory(session.Id, source);
+
+ sourceDirectory.Add(source, directory);
+ List<string> cpFiles = new List<string>();
+ copiedFiles.Add(source, cpFiles);
+ foreach (RevisionFile file in pair.Value)
+ {
+ if (disposed)
+ {
+ // if we're closed, abort file copy
+ WriteToInfoStream("doUpdate(): detected client was closed); abort file copy");
+ return;
+ }
+
+ Stream input = null;
+ IndexOutput output = null;
+ try
+ {
+ input = replicator.ObtainFile(session.Id, source, file.FileName);
+ output = directory.CreateOutput(file.FileName, IOContext.DEFAULT);
+
+ CopyBytes(output, input);
+
+ cpFiles.Add(file.FileName);
+ // TODO add some validation, on size / checksum
+ }
+ finally
+ {
+ IOUtils.Dispose(input, output);
+ }
+ }
+ // only notify if all required files were successfully obtained.
+ notify = true;
+ }
+ }
+ finally
+ {
+ if (session != null)
+ {
+ try
+ {
+ replicator.Release(session.Id);
+ }
+ finally
+ {
+ if (!notify)
+ {
+ // cleanup after ourselves
+ IOUtils.Dispose(sourceDirectory.Values);
+ factory.CleanupSession(session.Id);
+ }
+ }
+ }
+ }
+
+ // notify outside the try-finally above, so the session is released sooner.
+ // the handler may take time to finish acting on the copied files, but the
+ // session itself is no longer needed.
+ try
+ {
+ if (notify && !disposed)
+ { // no use to notify if we are closed already
+ handler.RevisionReady(session.Version, session.SourceFiles, new ReadOnlyDictionary<string, IList<string>>(copiedFiles), sourceDirectory);
+ }
+ }
+ finally
+ {
+ IOUtils.Dispose(sourceDirectory.Values);
+ //TODO: Resharper Message, Expression is always true -> Verify and if so then we can remove the null check.
+ if (session != null)
+ {
+ factory.CleanupSession(session.Id);
+ }
+ }
+
+ }
+
+ /// <exception cref="IOException"></exception>
+ private void CopyBytes(IndexOutput output, Stream input)
+ {
+ int numBytes;
+ while ((numBytes = input.Read(copyBuffer, 0, copyBuffer.Length)) > 0) {
+ output.WriteBytes(copyBuffer, 0, numBytes);
+ }
+ }
+
+ //.NET Note: Utility Method
+ private void WriteToInfoStream(string message)
+ {
+ if (infoStream.IsEnabled(INFO_STREAM_COMPONENT))
+ infoStream.Message(INFO_STREAM_COMPONENT, message);
+ }
+
+ /// <summary>
+ /// Returns the files required for replication. By default, this method returns
+ /// all files that exist in the new revision, but not in the handler.
+ /// </summary>
+ /// <param name="newRevisionFiles"></param>
+ /// <returns></returns>
+ private IDictionary<string, IList<RevisionFile>> RequiredFiles(IDictionary<string, IList<RevisionFile>> newRevisionFiles)
+ {
+ #region Java
+ //JAVA: protected Map<String,List<RevisionFile>> requiredFiles(Map<String,List<RevisionFile>> newRevisionFiles) {
+ //JAVA: Map<String,List<RevisionFile>> handlerRevisionFiles = handler.currentRevisionFiles();
+ //JAVA: if (handlerRevisionFiles == null) {
+ //JAVA: return newRevisionFiles;
+ //JAVA: }
+ //JAVA:
+ //JAVA: Map<String,List<RevisionFile>> requiredFiles = new HashMap<>();
+ //JAVA: for (Entry<String,List<RevisionFile>> e : handlerRevisionFiles.entrySet()) {
+ //JAVA: // put the handler files in a Set, for faster contains() checks later
+ //JAVA: Set<String> handlerFiles = new HashSet<>();
+ //JAVA: for (RevisionFile file : e.getValue()) {
+ //JAVA: handlerFiles.add(file.fileName);
+ //JAVA: }
+ //JAVA:
+ //JAVA: // make sure to preserve revisionFiles order
+ //JAVA: ArrayList<RevisionFile> res = new ArrayList<>();
+ //JAVA: String source = e.getKey();
+ //JAVA: assert newRevisionFiles.containsKey(source) : "source not found in newRevisionFiles: " + newRevisionFiles;
+ //JAVA: for (RevisionFile file : newRevisionFiles.get(source)) {
+ //JAVA: if (!handlerFiles.contains(file.fileName)) {
+ //JAVA: res.add(file);
+ //JAVA: }
+ //JAVA: }
+ //JAVA: requiredFiles.put(source, res);
+ //JAVA: }
+ //JAVA:
+ //JAVA: return requiredFiles;
+ //JAVA: }
+ #endregion
+
+ IDictionary<string, IList<RevisionFile>> handlerRevisionFiles = handler.CurrentRevisionFiles;
+ if (handlerRevisionFiles == null)
+ return newRevisionFiles;
+
+ Dictionary<string, IList<RevisionFile>> requiredFiles = new Dictionary<string, IList<RevisionFile>>();
+ foreach (KeyValuePair<string, IList<RevisionFile>> pair in handlerRevisionFiles)
+ {
+ // put the handler files in a Set, for faster contains() checks later
+ HashSet<string> handlerFiles = new HashSet<string>(pair.Value.Select(v => v.FileName));
+
+ // make sure to preserve revisionFiles order
+ string source = pair.Key;
+ Debug.Assert(newRevisionFiles.ContainsKey(source), string.Format("source not found in newRevisionFiles: {0}", newRevisionFiles));
+ List<RevisionFile> res = newRevisionFiles[source]
+ .Where(file => !handlerFiles.Contains(file.FileName))
+ .ToList();
+ requiredFiles.Add(source, res);
+ }
+ return requiredFiles;
+ }
+
+ /// <summary>
+ /// Start the update thread with the specified interval in milliseconds. For
+ /// debugging purposes, you can optionally set the name to set on
+ /// <see cref="ReplicationThread.Name"/>. If you pass <code>null</code>, a default name
+ /// will be set.
+ /// </summary>
+ /// <exception cref="InvalidOperationException"> if the thread has already been started </exception>
+ public void StartUpdateThread(long intervalMillis, string threadName)
+ {
+ #region Java
+ //JAVA: public synchronized void startUpdateThread(long intervalMillis, String threadName) {
+ //JAVA: ensureOpen();
+ //JAVA: if (updateThread != null && updateThread.isAlive()) {
+ //JAVA: throw new IllegalStateException(
+ //JAVA: "cannot start an update thread when one is running, must first call 'stopUpdateThread()'");
+ //JAVA: }
+ //JAVA: threadName = threadName == null ? INFO_STREAM_COMPONENT : "ReplicationThread-" + threadName;
+ //JAVA: updateThread = new ReplicationThread(intervalMillis);
+ //JAVA: updateThread.setName(threadName);
+ //JAVA: updateThread.start();
+ //JAVA: // we rely on isAlive to return true in isUpdateThreadAlive, assert to be on the safe side
+ //JAVA: assert updateThread.isAlive() : "updateThread started but not alive?";
+ //JAVA: }
+ #endregion
+
+ EnsureOpen();
+ if (updateThread != null && updateThread.IsAlive)
+ throw new InvalidOperationException("cannot start an update thread when one is running, must first call 'stopUpdateThread()'");
+
+ threadName = threadName == null ? INFO_STREAM_COMPONENT : "ReplicationThread-" + threadName;
+ updateThread = new ReplicationThread(intervalMillis, threadName, DoUpdate, HandleUpdateException, updateLock);
+ updateThread.Start();
+ // we rely on isAlive to return true in isUpdateThreadAlive, assert to be on the safe side
+ Debug.Assert(updateThread.IsAlive, "updateThread started but not alive?");
+ }
+
+ /// <summary>
+ /// Stop the update thread. If the update thread is not running, silently does
+ /// nothing. This method returns after the update thread has stopped.
+ /// </summary>
+ public void StopUpdateThread()
+ {
+ #region Java
+ //JAVA: public synchronized void stopUpdateThread() {
+ //JAVA: if (updateThread != null) {
+ //JAVA: // this will trigger the thread to terminate if it awaits the lock.
+ //JAVA: // otherwise, if it's in the middle of replication, we wait for it to
+ //JAVA: // stop.
+ //JAVA: updateThread.stop.countDown();
+ //JAVA: try {
+ //JAVA: updateThread.join();
+ //JAVA: } catch (InterruptedException e) {
+ //JAVA: Thread.currentThread().interrupt();
+ //JAVA: throw new ThreadInterruptedException(e);
+ //JAVA: }
+ //JAVA: updateThread = null;
+ //JAVA: }
+ //JAVA: }
+ #endregion
+
+ // this will trigger the thread to terminate if it awaits the lock.
+ // otherwise, if it's in the middle of replication, we wait for it to
+ // stop.
+ if (updateThread != null)
+ updateThread.Stop();
+ updateThread = null;
+ }
+
+ /// <summary>
+ /// Returns true if the update thread is alive. The update thread is alive if
+ /// it has been <see cref="StartUpdateThread"/> and not
+ /// <see cref="StopUpdateThread"/>, as well as didn't hit an error which
+ /// caused it to terminate (i.e. <see cref="HandleUpdateException"/>
+ /// threw the exception further).
+ /// </summary>
+ public bool IsUpdateThreadAlive
+ {
+ get { return updateThread != null && updateThread.IsAlive; }
+ }
+
+ /// <summary>Throws <see cref="ObjectDisposedException"/> if the client has already been disposed.</summary>
+ protected virtual void EnsureOpen()
+ {
+ if (!disposed)
+ return;
+
+ throw new ObjectDisposedException("this update client has already been closed");
+ }
+
+ /// <summary>
+ /// Called when an exception is hit by the replication thread. The default
+ /// implementation prints the full stacktrace to the <seealso cref="InfoStream"/> set in
+ /// <seealso cref="InfoStream"/>, or the <see cref="Util.InfoStream.Default"/>
+ /// one. You can override to log the exception elswhere.
+ /// </summary>
+ /// <remarks>
+ /// If you override this method to throw the exception further,
+ /// the replication thread will be terminated. The only way to restart it is to
+ /// call <seealso cref="StopUpdateThread"/> followed by
+ /// <seealso cref="StartUpdateThread"/>.
+ /// </remarks>
+ protected virtual void HandleUpdateException(Exception exception)
+ {
+ WriteToInfoStream(string.Format("an error occurred during revision update: {0}", exception));
+ }
+
+ /// <summary>
+ /// Executes the update operation immediately, irregardess if an update thread
+ /// is running or not.
+ /// </summary>
+ /// <exception cref="IOException"></exception>
+ public void UpdateNow()
+ {
+ EnsureOpen();
+ if (updateThread != null)
+ {
+ //NOTE: We have a worker running, we use that to perform the work instead by requesting it to run
+ // it's cycle immidiately.
+ updateThread.ExecuteImmediately();
+ return;
+ }
+
+ //NOTE: We don't have a worker running, so we just do the work.
+ updateLock.Lock();
+ try
+ {
+ DoUpdate();
+ }
+ finally
+ {
+ updateLock.Unlock();
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposed)
+ return;
+
+ StopUpdateThread();
+ disposed = true;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ public override string ToString()
+ {
+ if (updateThread == null)
+ return "ReplicationClient";
+ return string.Format("ReplicationClient ({0})", updateThread.Name);
+ }
+
+ //Note: LUCENENET specific, .NET does not work with Threads in the same way as Java does, so we mimic the same behavior using the ThreadPool instead.
+ private class ReplicationThread
+ {
+ #region Java
+ //JAVA: private class ReplicationThread extends Thread {
+ //JAVA: private final long interval;
+ //JAVA: // client uses this to stop us
+ //JAVA: final CountDownLatch stop = new CountDownLatch(1);
+ //JAVA:
+ //JAVA: public ReplicationThread(long interval) {
+ //JAVA: this.interval = interval;
+ //JAVA: }
+ //JAVA:
+ //JAVA: @SuppressWarnings("synthetic-access")
+ //JAVA: @Override
+ //JAVA: public void run() {
+ //JAVA: while (true) {
+ //JAVA: long time = System.currentTimeMillis();
+ //JAVA: updateLock.lock();
+ //JAVA: try {
+ //JAVA: doUpdate();
+ //JAVA: } catch (Throwable t) {
+ //JAVA: handleUpdateException(t);
+ //JAVA: } finally {
+ //JAVA: updateLock.unlock();
+ //JAVA: }
+ //JAVA: time = System.currentTimeMillis() - time;
+ //JAVA:
+ //JAVA: // adjust timeout to compensate the time spent doing the replication.
+ //JAVA: final long timeout = interval - time;
+ //JAVA: if (timeout > 0) {
+ //JAVA: try {
+ //JAVA: // this will return immediately if we were ordered to stop (count=0)
+ //JAVA: // or the timeout has elapsed. if it returns true, it means count=0,
+ //JAVA: // so terminate.
+ //JAVA: if (stop.await(timeout, TimeUnit.MILLISECONDS)) {
+ //JAVA: return;
+ //JAVA: }
+ //JAVA: } catch (InterruptedException e) {
+ //JAVA: // if we were interruted, somebody wants to terminate us, so just
+ //JAVA: // throw the exception further.
+ //JAVA: Thread.currentThread().interrupt();
+ //JAVA: throw new ThreadInterruptedException(e);
+ //JAVA: }
+ //JAVA: }
+ //JAVA: }
+ //JAVA: }
+ //JAVA: }
+ #endregion
+
+ private readonly Action doUpdate;
+ private readonly Action<Exception> handleException;
+ private readonly ReentrantLock @lock;
+ private readonly object controlLock = new object();
+
+ private readonly long interval;
+ private readonly AutoResetEvent handle = new AutoResetEvent(false);
+
+ private AutoResetEvent stopHandle;
+
+ /// <summary>
+ /// Gets or sets the name
+ /// </summary>
+ public string Name { get; private set; }
+
+ /// <summary>
+ ///
+ /// </summary>
+ /// <param name="intervalMillis"></param>
+ /// <param name="threadName"></param>
+ /// <param name="doUpdate"></param>
+ /// <param name="handleException"></param>
+ /// <param name="lock"></param>
+ public ReplicationThread(long intervalMillis, string threadName, Action doUpdate, Action<Exception> handleException, ReentrantLock @lock)
+ {
+ this.doUpdate = doUpdate;
+ this.handleException = handleException;
+ this.@lock = @lock;
+ Name = threadName;
+ this.interval = intervalMillis;
+ }
+
+ /// <summary>
+ ///
+ /// </summary>
+ public bool IsAlive { get; private set; }
+
+ /// <summary>
+ ///
+ /// </summary>
+ public void Start()
+ {
+ lock (controlLock)
+ {
+ if (IsAlive)
+ return;
+ IsAlive = true;
+ }
+ RegisterWait(interval);
+ }
+
+ /// <summary>
+ ///
+ /// </summary>
+ public void Stop()
+ {
+ lock (controlLock)
+ {
+ if (!IsAlive)
+ return;
+ IsAlive = false;
+ }
+ stopHandle = new AutoResetEvent(false);
+
+ //NOTE: Execute any outstanding, this execution will terminate almost instantaniously if it's not already running.
+ ExecuteImmediately();
+
+ stopHandle.WaitOne();
+ stopHandle = null;
+ }
+
+ /// <summary>
+ /// Executes the next cycle of work immediately
+ /// </summary>
+ public void ExecuteImmediately()
+ {
+ handle.Set();
+ }
+
+ private void RegisterWait(long timeout)
+ {
+ //NOTE: We don't care about timedout as it can either be because we was requested to run immidiately or stop.
+ if (IsAlive)
+ ThreadPool.RegisterWaitForSingleObject(handle, (state, timedout) => Run(), null, timeout, true);
+ else
+ SignalStop();
+ }
+
+ private void SignalStop()
+ {
+ if (stopHandle != null)
+ stopHandle.Set();
+ }
+
+ private void Run()
+ {
+ if (!IsAlive)
+ {
+ SignalStop();
+ return;
+ }
+
+ Stopwatch timer = Stopwatch.StartNew();
+ @lock.Lock();
+ try
+ {
+ doUpdate();
+ }
+ catch (Exception exception)
+ {
+ handleException(exception);
+ }
+ finally
+ {
+ @lock.Unlock();
+
+ timer.Stop();
+ long driftAdjusted = Math.Max(interval - timer.ElapsedMilliseconds, 0);
+ if (IsAlive)
+ RegisterWait(driftAdjusted);
+ else
+ SignalStop();
+ }
+ }
+ }
+
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Replicator/Replicator.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Replicator/Replicator.cs b/src/Lucene.Net.Replicator/Replicator.cs
new file mode 100644
index 0000000..af7ef51
--- /dev/null
+++ b/src/Lucene.Net.Replicator/Replicator.cs
@@ -0,0 +1,91 @@
+//STATUS: DRAFT - 4.8.0
+using System;
+using System.IO;
+
+namespace Lucene.Net.Replicator
+{
+ /*
+ * 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.
+ */
+
+ /// <summary>
+ /// An interface for replicating files. Allows a producer to
+ /// <see cref="Publish"/> <see cref="IRevision"/>s and consumers to
+ /// <see cref="CheckForUpdate"/>. When a client needs to be
+ /// updated, it is given a <see cref="SessionToken"/> through which it can
+ /// <see cref="ObtainFile"/> the files of that
+ /// revision. After the client has finished obtaining all the files, it should
+ /// <see cref="Release"/> the given session, so that the files can be
+ /// reclaimed if they are not needed anymore.
+ /// <p>
+ /// A client is always updated to the newest revision available. That is, if a
+ /// client is on revision <em>r1</em> and revisions <em>r2</em> and <em>r3</em>
+ /// were published, then when the cllient will next check for update, it will
+ /// receive <em>r3</em>.
+ /// </p>
+ /// </summary>
+ /// <remarks>
+ /// Lucene.Experimental
+ /// </remarks>
+ public interface IReplicator : IDisposable
+ {
+ /// <summary>
+ /// Publish a new <see cref="IRevision"/> for consumption by clients. It is the
+ /// caller's responsibility to verify that the revision files exist and can be
+ /// read by clients. When the revision is no longer needed, it will be
+ /// <see cref="Release"/>d by the replicator.
+ /// </summary>
+ /// <param name="revision">The <see cref="IRevision"/> to publish.</param>
+ /// <exception cref="IOException"></exception>
+ void Publish(IRevision revision);
+
+ /// <summary>
+ /// Check whether the given version is up-to-date and returns a
+ /// <see cref="SessionToken"/> which can be used for fetching the revision files,
+ /// otherwise returns <code>null</code>.
+ /// </summary>
+ /// <remarks>
+ /// When the returned session token is no longer needed, you
+ /// should call <see cref="Release"/> so that the session resources can be
+ /// reclaimed, including the revision files.
+ /// </remarks>
+ /// <param name="currentVersion"></param>
+ /// <returns></returns>
+ /// <exception cref="IOException"></exception>
+ SessionToken CheckForUpdate(string currentVersion);// throws IOException;
+
+ /// <summary>
+ /// Notify that the specified <see cref="SessionToken"/> is no longer needed by the caller.
+ /// </summary>
+ /// <param name="sessionId"></param>
+ /// <exception cref="IOException"></exception>
+ void Release(string sessionId);
+
+ /// <summary>
+ /// Returns an <see cref="Stream"/> for the requested file and source in the
+ /// context of the given <see cref="SessionToken.Id"/>.
+ /// </summary>
+ /// <remarks>
+ /// It is the caller's responsibility to call <see cref="IDisposable.Dispose"/> on the returned stream.
+ /// </remarks>
+ /// <param name="sessionId"></param>
+ /// <param name="source"></param>
+ /// <param name="fileName"></param>
+ /// <returns></returns>
+ /// <exception cref="SessionExpiredException">The specified session has already expired</exception>
+ Stream ObtainFile(string sessionId, string source, string fileName);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Replicator/Revision.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Replicator/Revision.cs b/src/Lucene.Net.Replicator/Revision.cs
new file mode 100644
index 0000000..3d6fe19
--- /dev/null
+++ b/src/Lucene.Net.Replicator/Revision.cs
@@ -0,0 +1,81 @@
+//STATUS: DRAFT - 4.8.0
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Lucene.Net.Replicator
+{
+ /*
+ * 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.
+ */
+
+ /// <summary>
+ /// A revision comprises lists of files that come from different sources and need
+ /// to be replicated together to e.g. guarantee that all resources are in sync.
+ /// In most cases an application will replicate a single index, and so the
+ /// revision will contain files from a single source. However, some applications
+ /// may require to treat a collection of indexes as a single entity so that the
+ /// files from all sources are replicated together, to guarantee consistency
+ /// beween them. For example, an application which indexes facets will need to
+ /// replicate both the search and taxonomy indexes together, to guarantee that
+ /// they match at the client side.
+ /// </summary>
+ /// <remarks>
+ /// Lucene.Experimental
+ /// </remarks>
+ public interface IRevision : IComparable<IRevision>
+ {
+ /// <summary>
+ /// Returns a string representation of the version of this revision. The
+ /// version is used by <see cref="CompareTo"/> as well as to
+ /// serialize/deserialize revision information. Therefore it must be self
+ /// descriptive as well as be able to identify one revision from another.
+ /// </summary>
+ string Version { get; }
+
+ /// <summary>
+ /// Returns the files that comprise this revision, as a mapping from a source
+ /// to a list of files.
+ /// </summary>
+ IDictionary<string, IList<RevisionFile>> SourceFiles { get; }
+
+ /// <summary>
+ /// Compares the revision to the given version string. Behaves like
+ /// <see cref="IComparable{T}.CompareTo"/>
+ /// </summary>
+ int CompareTo(string version);
+
+ /// <summary>
+ /// Returns an {@link IndexInput} for the given fileName and source. It is the
+ /// caller's respnsibility to close the {@link IndexInput} when it has been
+ /// consumed.
+ /// </summary>
+ /// <param name="source"></param>
+ /// <param name="fileName"></param>
+ /// <returns></returns>
+ /// <exception cref="IOException"></exception>
+ //TODO: Stream or IndexInput?
+ Stream Open(string source, string fileName);
+
+ /// <summary>
+ /// Called when this revision can be safely released, i.e. where there are no
+ /// more references to it.
+ /// </summary>
+ /// <exception cref="IOException"></exception>
+ void Release();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Replicator/RevisionFile.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Replicator/RevisionFile.cs b/src/Lucene.Net.Replicator/RevisionFile.cs
new file mode 100644
index 0000000..abd7aff
--- /dev/null
+++ b/src/Lucene.Net.Replicator/RevisionFile.cs
@@ -0,0 +1,87 @@
+//STATUS: DRAFT - 4.8.0
+
+using System;
+
+namespace Lucene.Net.Replicator
+{
+ /*
+ * 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.
+ */
+
+ /// <summary>
+ /// Describes a file in a <see cref="IRevision"/>. A file has a source, which allows a
+ /// single revision to contain files from multiple sources (e.g. multiple indexes).
+ /// </summary>
+ /// <remarks>
+ /// Lucene.Experimental
+ /// </remarks>
+ public class RevisionFile : IEquatable<RevisionFile>
+ {
+ /// <summary>
+ /// Gets the name of the file.
+ /// </summary>
+ public string FileName { get; private set; }
+
+ //TODO: can this be readonly?
+ /// <summary>
+ /// Gets or sets the length of the file denoted by <see cref="FileName"/>.
+ /// </summary>
+ public long Length { get; set; }
+
+ /// <summary>
+ /// Constructor with the given file name and optionally length.
+ /// </summary>
+ /// <param name="fileName"></param>
+ /// <param name="length">Optional, the length of the file.</param>
+ public RevisionFile(string fileName, long length = -1)
+ {
+ if (string.IsNullOrEmpty(fileName)) throw new ArgumentException("fileName must not be null or empty", "fileName");
+
+ FileName = fileName;
+ Length = length;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("fileName={0} length={1}", FileName, Length);
+ }
+
+ #region Resharper Generated Code
+ public bool Equals(RevisionFile other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return string.Equals(FileName, other.FileName) && Length == other.Length;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != this.GetType()) return false;
+ return Equals((RevisionFile)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (FileName.GetHashCode() * 397) ^ Length.GetHashCode();
+ }
+ }
+ #endregion
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Replicator/SessionExpiredException.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Replicator/SessionExpiredException.cs b/src/Lucene.Net.Replicator/SessionExpiredException.cs
new file mode 100644
index 0000000..38eed6c
--- /dev/null
+++ b/src/Lucene.Net.Replicator/SessionExpiredException.cs
@@ -0,0 +1,58 @@
+//STATUS: DRAFT - 4.8.0
+
+using System;
+using System.IO;
+using System.Runtime.Serialization;
+
+namespace Lucene.Net.Replicator
+{
+ /*
+ * 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.
+ */
+
+ /// <summary>
+ /// Exception indicating that a revision update session was expired due to lack of activity.
+ /// </summary>
+ /// <remarks>
+ /// <see cref="LocalReplicator.DEFAULT_SESSION_EXPIRATION_THRESHOLD"/>
+ /// <see cref="LocalReplicator.ExpirationThreshold"/>
+ ///
+ /// Lucene.Experimental
+ /// </remarks>
+ public class SessionExpiredException : IOException
+ {
+ //
+ // For guidelines regarding the creation of new exception types, see
+ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
+ // and
+ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
+ //
+
+ public SessionExpiredException()
+ {
+ }
+
+ public SessionExpiredException(string message)
+ : base(message)
+ {
+ }
+
+ public SessionExpiredException(string message, Exception inner)
+ : base(message, inner)
+ {
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Replicator/SessionToken.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Replicator/SessionToken.cs b/src/Lucene.Net.Replicator/SessionToken.cs
new file mode 100644
index 0000000..a9440d7
--- /dev/null
+++ b/src/Lucene.Net.Replicator/SessionToken.cs
@@ -0,0 +1,129 @@
+//STATUS: DRAFT - 4.8.0
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using Lucene.Net.Store;
+using Lucene.Net.Support.IO;
+
+namespace Lucene.Net.Replicator
+{
+ /*
+ * 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.
+ */
+
+ /// <summary>
+ /// Token for a replication session, for guaranteeing that source replicated
+ /// files will be kept safe until the replication completes.
+ /// </summary>
+ /// <remarks>
+ /// <see cref="IReplicator.CheckForUpdate"/>
+ /// <see cref="IReplicator.Release"/>
+ /// <see cref="LocalReplicator.DEFAULT_SESSION_EXPIRATION_THRESHOLD"/>
+ ///
+ /// Lucene.Experimental
+ /// </remarks>
+ public sealed class SessionToken
+ {
+ /// <summary>
+ /// Id of this session.
+ /// Should be passed when releasing the session, thereby acknowledging the
+ /// <see cref="IReplicator"/> that this session is no longer in use.
+ /// <see cref="IReplicator.Release"/>
+ /// </summary>
+ public string Id { get; private set; }
+
+ /// <summary>
+ /// <see cref="IRevision.Version"/>
+ /// </summary>
+ public string Version { get; private set; }
+
+ /// <summary>
+ /// <see cref="IRevision.SourceFiles"/>
+ /// </summary>
+ public IDictionary<string, IList<RevisionFile>> SourceFiles { get; private set; }
+
+ /// <summary>
+ /// Constructor which deserializes from the given <see cref="DataInput"/>.
+ /// </summary>
+ /// <param name="reader"></param>
+ /// <exception cref="IOException"></exception>
+ public SessionToken(DataInputStream reader)
+ {
+ Id = reader.ReadUTF();
+ Version = reader.ReadUTF();
+
+ var sourceFiles = new Dictionary<string, IList<RevisionFile>>();
+ int numSources = reader.ReadInt32();
+ while (numSources > 0)
+ {
+ string source = reader.ReadUTF();
+ int numFiles = reader.ReadInt32();
+
+ List<RevisionFile> files = new List<RevisionFile>(numFiles);
+ for (int i = 0; i < numFiles; i++)
+ {
+ files.Add(new RevisionFile(reader.ReadUTF(), reader.ReadInt64()));
+ }
+ sourceFiles.Add(source, files);
+ --numSources;
+ }
+ SourceFiles = sourceFiles;
+ }
+
+ /// <summary>
+ /// Constructor with the given id and revision.
+ /// </summary>
+ /// <param name="id"></param>
+ /// <param name="revision"></param>
+ /// <exception cref="IOException"></exception>
+ public SessionToken(string id, IRevision revision)
+ {
+ Id = id;
+ Version = revision.Version;
+ SourceFiles = revision.SourceFiles;
+ }
+
+ /// <summary>
+ /// Serialize the token data for communication between server and client.
+ /// </summary>
+ /// <param name="writer"></param>
+ /// <exception cref="IOException"></exception>
+ public void Serialize(DataOutputStream writer)
+ {
+ writer.WriteUTF(Id);
+ writer.WriteUTF(Version);
+ writer.WriteInt32(SourceFiles.Count);
+
+ foreach (KeyValuePair<string, IList<RevisionFile>> pair in SourceFiles)
+ {
+ writer.WriteUTF(pair.Key);
+ writer.WriteInt32(pair.Value.Count);
+ foreach (RevisionFile file in pair.Value)
+ {
+ writer.WriteUTF(file.FileName);
+ writer.WriteInt64(file.Length);
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ return string.Format("id={0} version={1} files={2}", Id, Version, SourceFiles);
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Replicator/packages.config
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Replicator/packages.config b/src/Lucene.Net.Replicator/packages.config
new file mode 100644
index 0000000..3e14be6
--- /dev/null
+++ b/src/Lucene.Net.Replicator/packages.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net451" />
+</packages>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Tests.Replicator/Http/HttpReplicatorTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Replicator/Http/HttpReplicatorTest.cs b/src/Lucene.Net.Tests.Replicator/Http/HttpReplicatorTest.cs
new file mode 100644
index 0000000..3d116f9
--- /dev/null
+++ b/src/Lucene.Net.Tests.Replicator/Http/HttpReplicatorTest.cs
@@ -0,0 +1,104 @@
+//STATUS: DRAFT - 4.8.0
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using Lucene.Net.Documents;
+using Lucene.Net.Index;
+using Lucene.Net.Replicator;
+using Lucene.Net.Replicator.Http;
+using Lucene.Net.Support;
+using Lucene.Net.Util;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using NUnit.Framework;
+using Directory = Lucene.Net.Store.Directory;
+
+namespace Lucene.Net.Tests.Replicator.Http
+{
+ public class HttpReplicatorTest : ReplicatorTestCase
+ {
+ private DirectoryInfo clientWorkDir;
+ private IReplicator serverReplicator;
+ private IndexWriter writer;
+ private DirectoryReader reader;
+
+ private int port;
+ private string host;
+ private TestServer server;
+
+ private Directory serverIndexDir;
+ private Directory handlerIndexDir;
+
+ private void StartServer()
+ {
+ ReplicationService service = new ReplicationService(new Dictionary<string, IReplicator> { { "s1", serverReplicator } });
+
+ server = NewHttpServer<ReplicationServlet>(service);
+ port = ServerPort(server);
+ host = ServerHost(server);
+ }
+
+ public override void SetUp()
+ {
+ base.SetUp();
+ //JAVA: System.setProperty("org.eclipse.jetty.LEVEL", "DEBUG"); // sets stderr logging to DEBUG level
+ clientWorkDir = CreateTempDir("httpReplicatorTest");
+ handlerIndexDir = NewDirectory();
+ serverIndexDir = NewDirectory();
+ serverReplicator = new LocalReplicator();
+ StartServer();
+
+ IndexWriterConfig conf = NewIndexWriterConfig(TEST_VERSION_CURRENT, null);
+ conf.IndexDeletionPolicy = new SnapshotDeletionPolicy(conf.IndexDeletionPolicy);
+ writer = new IndexWriter(serverIndexDir, conf);
+ reader = DirectoryReader.Open(writer, false);
+ }
+
+ public override void TearDown()
+ {
+ StopHttpServer(server);
+ IOUtils.Dispose(reader, writer, handlerIndexDir, serverIndexDir);
+ //JAVA: System.clearProperty("org.eclipse.jetty.LEVEL");
+ base.TearDown();
+ }
+
+ private void PublishRevision(int id)
+ {
+ Document doc = new Document();
+ writer.AddDocument(doc);
+ writer.SetCommitData(Collections.SingletonMap("ID", id.ToString("X")));
+ writer.Commit();
+ serverReplicator.Publish(new IndexRevision(writer));
+ }
+
+ private void ReopenReader()
+ {
+ DirectoryReader newReader = DirectoryReader.OpenIfChanged(reader);
+ assertNotNull(newReader);
+ reader.Dispose();
+ reader = newReader;
+ }
+
+
+ [Test]
+ public void TestBasic()
+ {
+ IReplicator replicator = new HttpReplicator(host, port, ReplicationService.REPLICATION_CONTEXT + "/s1", server.CreateHandler());
+ ReplicationClient client = new ReplicationClient(replicator, new IndexReplicationHandler(handlerIndexDir, null),
+ new PerSessionDirectoryFactory(clientWorkDir.FullName));
+
+ PublishRevision(1);
+ client.UpdateNow();
+ ReopenReader();
+ assertEquals(1, int.Parse(reader.IndexCommit.UserData["ID"], NumberStyles.HexNumber));
+
+ PublishRevision(2);
+ client.UpdateNow();
+ ReopenReader();
+ assertEquals(2, int.Parse(reader.IndexCommit.UserData["ID"], NumberStyles.HexNumber));
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Tests.Replicator/Http/ReplicationServlet.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Replicator/Http/ReplicationServlet.cs b/src/Lucene.Net.Tests.Replicator/Http/ReplicationServlet.cs
new file mode 100644
index 0000000..63420a6
--- /dev/null
+++ b/src/Lucene.Net.Tests.Replicator/Http/ReplicationServlet.cs
@@ -0,0 +1,22 @@
+//STATUS: DRAFT - 4.8.0
+
+using System.Threading.Tasks;
+using Lucene.Net.Replicator.AspNetCore;
+using Lucene.Net.Replicator.Http;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+
+namespace Lucene.Net.Tests.Replicator.Http
+{
+ public class ReplicationServlet
+ {
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ReplicationService service)
+ {
+ app.Run(async context =>
+ {
+ await Task.Yield();
+ service.Perform(context.Request, context.Response);
+ });
+ }
+ }
+}
\ No newline at end of file