diff --git a/Testcontainers.sln b/Testcontainers.sln
index 14af32b18..4bcc83ad4 100644
--- a/Testcontainers.sln
+++ b/Testcontainers.sln
@@ -105,6 +105,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ServiceBus",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Sftp", "src\Testcontainers.Sftp\Testcontainers.Sftp.csproj", "{7D5C6816-0DD2-4E13-A585-033B5D3C80D5}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tika", "src\Testcontainers.Tika\Testcontainers.Tika.csproj", "{AC084DE2-1857-E200-EF47-8C4ADEB2173D}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Weaviate", "src\Testcontainers.Weaviate\Testcontainers.Weaviate.csproj", "{68F8600D-24E9-4E03-9E25-5F6EB338EAC1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver", "src\Testcontainers.WebDriver\Testcontainers.WebDriver.csproj", "{64A87DE5-29B0-4A54-9E74-560484D8C7C0}"
@@ -233,14 +235,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver.Te
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Xunit.Tests", "tests\Testcontainers.Xunit.Tests\Testcontainers.Xunit.Tests.csproj", "{E901DF14-6F05-4FC2-825A-3055FAD33561}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tika.Tests", "tests\Testcontainers.Tika.Tests\Testcontainers.Tika.Tests.csproj", "{FDD2E9F5-DAAC-2F8F-B8DD-6924CD1D9091}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5365F780-0E6C-41F0-B1B9-7DC34368F80C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5365F780-0E6C-41F0-B1B9-7DC34368F80C}.Debug|Any CPU.Build.0 = Debug|Any CPU
@@ -682,6 +683,17 @@ Global
{E901DF14-6F05-4FC2-825A-3055FAD33561}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E901DF14-6F05-4FC2-825A-3055FAD33561}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E901DF14-6F05-4FC2-825A-3055FAD33561}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AC084DE2-1857-E200-EF47-8C4ADEB2173D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AC084DE2-1857-E200-EF47-8C4ADEB2173D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AC084DE2-1857-E200-EF47-8C4ADEB2173D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AC084DE2-1857-E200-EF47-8C4ADEB2173D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FDD2E9F5-DAAC-2F8F-B8DD-6924CD1D9091}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FDD2E9F5-DAAC-2F8F-B8DD-6924CD1D9091}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FDD2E9F5-DAAC-2F8F-B8DD-6924CD1D9091}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FDD2E9F5-DAAC-2F8F-B8DD-6924CD1D9091}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
@@ -794,5 +806,7 @@ Global
{DDB41BC8-5826-4D97-9C5F-001151E3FFD6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{E901DF14-6F05-4FC2-825A-3055FAD33561} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
+ {AC084DE2-1857-E200-EF47-8C4ADEB2173D} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
+ {FDD2E9F5-DAAC-2F8F-B8DD-6924CD1D9091} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
EndGlobalSection
EndGlobal
diff --git a/src/Testcontainers.Tika/Testcontainers.Tika.csproj b/src/Testcontainers.Tika/Testcontainers.Tika.csproj
new file mode 100644
index 000000000..b87ee8f03
--- /dev/null
+++ b/src/Testcontainers.Tika/Testcontainers.Tika.csproj
@@ -0,0 +1,12 @@
+
+
+ net8.0;net9.0;netstandard2.0;netstandard2.1
+ latest
+
+
+
+
+
+
+
+
diff --git a/src/Testcontainers.Tika/TikaBuilder.cs b/src/Testcontainers.Tika/TikaBuilder.cs
new file mode 100644
index 000000000..1b1044e45
--- /dev/null
+++ b/src/Testcontainers.Tika/TikaBuilder.cs
@@ -0,0 +1,115 @@
+namespace Testcontainers.Tika;
+
+///
+[PublicAPI]
+public sealed class TikaBuilder : ContainerBuilder
+{
+ public const string TikaImage = "apache/tika:3.0.0.0-full";
+ public const ushort TikaHttpPort = 9998;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TikaBuilder()
+ : this(new TikaConfiguration())
+ {
+ DockerResourceConfiguration = Init().DockerResourceConfiguration;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ private TikaBuilder(TikaConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ DockerResourceConfiguration = resourceConfiguration;
+ }
+ ///
+
+ protected override TikaConfiguration DockerResourceConfiguration { get; }
+
+ ///
+ /// Sets the Tika server timeout.
+ ///
+ /// The timeout for the server in milliseconds.
+ /// A configured instance of .
+ public TikaBuilder WithTimeout(int timeout)
+ {
+ return Merge(DockerResourceConfiguration, new TikaConfiguration(timeout: timeout))
+ .WithEnvironment("TIKA_TIMEOUT", timeout.ToString());
+ }
+
+ public override TikaContainer Build()
+ {
+ Validate();
+ return new TikaContainer(DockerResourceConfiguration);
+ }
+
+ protected override TikaBuilder Init()
+ {
+ return base.Init()
+ .WithImage(TikaImage)
+ .WithPortBinding(TikaHttpPort, true)
+ .WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil()));
+ }
+
+ protected override TikaBuilder Clone(IResourceConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new TikaConfiguration(resourceConfiguration));
+ }
+
+ protected override TikaBuilder Clone(IContainerConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new TikaConfiguration(resourceConfiguration));
+ }
+
+ protected override TikaBuilder Merge(TikaConfiguration oldValue, TikaConfiguration newValue)
+ {
+ return new TikaBuilder(new TikaConfiguration(oldValue, newValue));
+ }
+
+ private sealed class WaitUntil : IWaitUntil
+ {
+ private const string HealthCheckPath = "tika";
+ private const int MaxRetryAttempts = 10;
+ private const int DelayInMilliseconds = 1000;
+
+ ///
+ /// Waits until the Tika server is available by checking the health check endpoint.
+ ///
+ /// The container instance to check.
+ ///
+ /// A task that represents the asynchronous operation. The task result contains a boolean indicating whether the Tika server is available.
+ ///
+ ///
+ /// This method sends HTTP GET requests to the Tika server's health check endpoint and retries up to a maximum number of times if the server is not available.
+ ///
+ public async Task UntilAsync(DotNet.Testcontainers.Containers.IContainer container)
+ {
+ string endpoint = $"http://{container.Hostname}:{container.GetMappedPublicPort(TikaBuilder.TikaHttpPort)}/{HealthCheckPath}";
+
+ using var client = new HttpClient();
+
+ for (int i = 0; i < MaxRetryAttempts; i++)
+ {
+ try
+ {
+ var response = await client.GetAsync(endpoint);
+
+ response.EnsureSuccessStatusCode();
+ string responseContent = await response.Content.ReadAsStringAsync(); // This is Tika Server (Apache Tika 3.0.0). Please PUT, volendo si può fare questo check
+ return true;
+ }
+ catch
+ {
+ // Ignore exceptions and retry
+ }
+
+ await Task.Delay(DelayInMilliseconds);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Testcontainers.Tika/TikaConfiguration.cs b/src/Testcontainers.Tika/TikaConfiguration.cs
new file mode 100644
index 000000000..b7b45bfa6
--- /dev/null
+++ b/src/Testcontainers.Tika/TikaConfiguration.cs
@@ -0,0 +1,54 @@
+namespace Testcontainers.Tika;
+
+///
+[PublicAPI]
+public sealed class TikaConfiguration : ContainerConfiguration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The timeout for the Tika server.
+ public TikaConfiguration(int timeout = 30000)
+ {
+ Timeout = timeout;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public TikaConfiguration(IResourceConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public TikaConfiguration(IContainerConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class,
+ /// combining properties from two existing configurations.
+ ///
+ /// The previous configuration values.
+ /// The new configuration values to merge with the old ones.
+ public TikaConfiguration(TikaConfiguration oldValue, TikaConfiguration newValue)
+ : base(oldValue, newValue)
+ {
+ // Combine values manually
+ Timeout = BuildConfiguration.Combine(oldValue.Timeout, newValue.Timeout);
+ }
+
+
+ ///
+ /// Gets the Tika server timeout.
+ ///
+ public int Timeout { get; }
+}
diff --git a/src/Testcontainers.Tika/TikaContainer.cs b/src/Testcontainers.Tika/TikaContainer.cs
new file mode 100644
index 000000000..31552c32a
--- /dev/null
+++ b/src/Testcontainers.Tika/TikaContainer.cs
@@ -0,0 +1,28 @@
+namespace Testcontainers.Tika;
+
+///
+[PublicAPI]
+public sealed class TikaContainer : DockerContainer
+{
+ private readonly TikaConfiguration _configuration;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The container configuration.
+ public TikaContainer(TikaConfiguration configuration)
+ : base(configuration)
+ {
+ _configuration = configuration;
+ }
+
+ ///
+ /// Gets the Tika connection string.
+ ///
+ /// The Tika connection string.
+ public string GetConnectionString()
+ {
+ var endpoint = new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(TikaBuilder.TikaHttpPort));
+ return endpoint.ToString();
+ }
+}
diff --git a/src/Testcontainers.Tika/Usings.cs b/src/Testcontainers.Tika/Usings.cs
new file mode 100644
index 000000000..ab394a51d
--- /dev/null
+++ b/src/Testcontainers.Tika/Usings.cs
@@ -0,0 +1,8 @@
+global using System;
+global using Docker.DotNet.Models;
+global using DotNet.Testcontainers.Builders;
+global using DotNet.Testcontainers.Configurations;
+global using DotNet.Testcontainers.Containers;
+global using JetBrains.Annotations;
+global using System.Threading.Tasks;
+global using System.Net.Http;
\ No newline at end of file
diff --git a/tests/Testcontainers.Tika.Tests/Testcontainers.Tika.Tests.csproj b/tests/Testcontainers.Tika.Tests/Testcontainers.Tika.Tests.csproj
new file mode 100644
index 000000000..e019d24ed
--- /dev/null
+++ b/tests/Testcontainers.Tika.Tests/Testcontainers.Tika.Tests.csproj
@@ -0,0 +1,19 @@
+
+
+ net9.0
+ false
+ false
+ Debug;Release
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Testcontainers.Tika.Tests/TikaContainerTest.cs b/tests/Testcontainers.Tika.Tests/TikaContainerTest.cs
new file mode 100644
index 000000000..525edd449
--- /dev/null
+++ b/tests/Testcontainers.Tika.Tests/TikaContainerTest.cs
@@ -0,0 +1,49 @@
+namespace Testcontainers.Tika.Tests;
+
+public sealed class TikaContainerTests : IAsyncLifetime
+{
+ private readonly TikaContainer _tikaContainer = new TikaBuilder().Build();
+
+ public Task InitializeAsync()
+ {
+ return _tikaContainer.StartAsync();
+ }
+
+ public Task DisposeAsync()
+ {
+ return _tikaContainer.DisposeAsync().AsTask();
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task GetConnectionStringReturnsValidUrl()
+ {
+ // When
+ var connectionString = await Task.Run(() => _tikaContainer.GetConnectionString());
+
+ // Then
+ Assert.False(string.IsNullOrEmpty(connectionString));
+ Assert.StartsWith("http://", connectionString, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task TikaHealthCheckShouldBeSuccessful()
+ {
+ {
+ // Given
+ var httpClient = new HttpClient();
+ var connectionString = await Task.Run(() => _tikaContainer.GetConnectionString());
+ var requestUrl = $"{connectionString}tika";
+
+ // When
+ var response = await httpClient.GetAsync(requestUrl);
+
+ // Then
+ response.EnsureSuccessStatusCode();
+ var content = await response.Content.ReadAsStringAsync();
+ Assert.False(string.IsNullOrEmpty(content));
+ Assert.StartsWith("This is Tika Server", content);
+ }
+ }
+}
diff --git a/tests/Testcontainers.Tika.Tests/Usings.cs b/tests/Testcontainers.Tika.Tests/Usings.cs
new file mode 100644
index 000000000..ed7fe333b
--- /dev/null
+++ b/tests/Testcontainers.Tika.Tests/Usings.cs
@@ -0,0 +1,5 @@
+global using DotNet.Testcontainers.Commons;
+global using System;
+global using System.Net.Http;
+global using System.Threading.Tasks;
+global using Xunit;