Initial commit

This commit is contained in:
2022-07-06 21:35:05 +02:00
commit b2494052ff
97220 changed files with 2449256 additions and 0 deletions
@@ -0,0 +1,98 @@
using System.Reflection;
using Autofac;
using Gos.Core.Search;
using Gos.Core.Search.Aggregations;
using Gos.Infrastructure.Search;
using Gos.Infrastructure.Search.Aggregations;
using Gos.Infrastructure.Search.Converters;
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Gos.Infrastructure.Search.QueryHandlers;
using Gos.ServiceModel.Enums;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Nest;
using Module = Autofac.Module;
namespace Gos.Infrastructure.CompositionRoot
{
public class InfrastructureModule : Module
{
private Assembly InfrastructureAssembly => GetType().Assembly;
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
RegisterCaches(builder);
RegisterConfiguration(builder);
RegisterSearch(builder);
}
private static void RegisterCaches(ContainerBuilder builder)
{
var memoryCache = new MemoryCache(new MemoryCacheOptions());
builder.RegisterInstance(memoryCache).As<IMemoryCache>().SingleInstance();
}
private static void RegisterConfiguration(ContainerBuilder builder)
{
var configuration = new ConfigurationBuilder().AddEnvironmentVariables().Build();
builder.RegisterInstance(configuration).As<IConfiguration>().SingleInstance();
}
private void RegisterSearch(ContainerBuilder builder)
{
// Client
builder.RegisterType<ElasticClientFactory>().SingleInstance();
builder.Register(
c =>
{
var factory = c.Resolve<ElasticClientFactory>();
return factory.CreateClient();
})
.As<IElasticClient>()
.SingleInstance();
// Index providers
builder.RegisterType<IndexProviderFactory>().As<IIndexProviderFactory>().SingleInstance();
builder.RegisterAssemblyTypes(InfrastructureAssembly)
.Where(t => typeof(IIndexProvider).IsAssignableFrom(t))
.AsImplementedInterfaces()
.SingleInstance();
// Search engine
builder.RegisterType<ElasticSearchEngine>().As<ISearchEngine>().SingleInstance();
// Entity-DTO converters
builder.RegisterType<EsDtoConverterFactory>().As<IEsDtoConverterFactory>().SingleInstance();
builder.RegisterAssemblyTypes(InfrastructureAssembly)
.Where(t => typeof(IEsDtoConverter).IsAssignableFrom(t))
.AsImplementedInterfaces()
.SingleInstance();
// Query builders
builder.RegisterType<QueryBuilderFactory>().As<IQueryBuilderFactory>().SingleInstance();
builder.RegisterAssemblyTypes(InfrastructureAssembly).AsClosedTypesOf(typeof(IQueryBuilder<>)).AsImplementedInterfaces().SingleInstance();
// Query handlers
builder.RegisterType<QueryHandlerFactory>().As<IQueryHandlerFactory>().SingleInstance();
builder.RegisterAssemblyTypes(InfrastructureAssembly).AsClosedTypesOf(typeof(IQueryHandler<,>)).AsImplementedInterfaces().SingleInstance();
// Aggregators
builder.RegisterType<AggregatorFactory>().As<IAggregatorFactory>().SingleInstance();
builder.RegisterType<DiscourseChannelAggregator>().Keyed<IAggregator>(AggregationType.DiscourseChannel).SingleInstance();
builder.RegisterType<DiscourseEventAggregator>().Keyed<IAggregator>(AggregationType.DiscourseEvent).SingleInstance();
builder.RegisterType<DiscourseRegionAggregator>().Keyed<IAggregator>(AggregationType.DiscourseRegion).SingleInstance();
builder.RegisterType<DiscourseTypeAggregator>().Keyed<IAggregator>(AggregationType.DiscourseType).SingleInstance();
builder.RegisterType<DiscourseYearAggregator>().Keyed<IAggregator>(AggregationType.DiscourseYear).SingleInstance();
builder.RegisterType<SpeakerAgeAggregator>().Keyed<IAggregator>(AggregationType.SpeakerAge).SingleInstance();
builder.RegisterType<SpeakerEducationAggregator>().Keyed<IAggregator>(AggregationType.SpeakerEducation).SingleInstance();
builder.RegisterType<SpeakerLanguageAggregator>().Keyed<IAggregator>(AggregationType.SpeakerLanguage).SingleInstance();
builder.RegisterType<SpeakerRegionAggregator>().Keyed<IAggregator>(AggregationType.SpeakerRegion).SingleInstance();
builder.RegisterType<SpeakerSexAggregator>().Keyed<IAggregator>(AggregationType.SpeakerSex).SingleInstance();
builder.RegisterType<PartOfSpeechAggregator>().Keyed<IAggregator>(AggregationType.PartOfSpeech).SingleInstance();
builder.RegisterType<LemmaAggregator>().Keyed<IAggregator>(AggregationType.Lemma).SingleInstance();
}
}
}
@@ -0,0 +1,34 @@
using System.Collections.Generic;
using Nest;
namespace Gos.Infrastructure.Extensions
{
public static class ElasticQueriesExtensions
{
public static QueryContainer ToBooleanAndQuery(this List<QueryContainer> queries)
{
return queries.Count switch
{
0 => null,
1 => queries[0],
_ => new BoolQuery()
{
Must = queries,
},
};
}
public static QueryContainer ToBooleanOrQuery(this List<QueryContainer> queries)
{
return queries.Count switch
{
0 => null,
1 => queries[0],
_ => new BoolQuery()
{
Should = queries,
},
};
}
}
}
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Gos.Core\Gos.Core.csproj" />
<ProjectReference Include="..\Gos.Services\Gos.Services.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="6.3.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="NEST" Version="7.17.0" />
</ItemGroup>
</Project>
@@ -0,0 +1,18 @@
using Autofac.Features.Indexed;
using Gos.Core.Search.Aggregations;
using Gos.ServiceModel.Enums;
namespace Gos.Infrastructure.Search.Aggregations
{
public class AggregatorFactory : IAggregatorFactory
{
private readonly IIndex<AggregationType, IAggregator> aggregators;
public AggregatorFactory(IIndex<AggregationType, IAggregator> aggregators)
{
this.aggregators = aggregators;
}
public IAggregator GetAggregator(AggregationType aggregationType) => aggregators[aggregationType];
}
}
@@ -0,0 +1,71 @@
using System.Collections.Generic;
using System.Linq;
using Gos.Core.Search.Aggregations;
using Gos.Core.Search.Queries;
using Gos.Infrastructure.Search.Dtos;
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.Aggregations
{
public abstract class BaseAggregator : IAggregator
{
private readonly IElasticClient elasticClient;
private readonly IIndexProviderFactory indexProviderFactory;
private readonly IQueryBuilderFactory queryBuilderFactory;
protected BaseAggregator(IElasticClient elasticClient, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
{
this.elasticClient = elasticClient;
this.indexProviderFactory = indexProviderFactory;
this.queryBuilderFactory = queryBuilderFactory;
}
protected abstract string FieldName { get; }
public IDictionary<string, int> Get<TQuery>(TQuery query)
where TQuery : Query
{
// Build query for elastic
var queryBuilder = queryBuilderFactory.GetBuilder<TQuery>();
var elasticQuery = queryBuilder.Build(query);
// Get and execute search request
var request = GetSearchRequest(elasticQuery);
var response = elasticClient.Search<EsConcordanceDto>(request);
// Read response
return ReadAggregation(response);
}
private SearchRequest GetSearchRequest(QueryContainer query)
{
var indexProvider = indexProviderFactory.GetProvider<EsConcordanceDto>();
var indexName = indexProvider.IndexName;
return new SearchRequest(indexName)
{
From = 0,
Size = 0,
Query = query,
Aggregations = new AggregationDictionary()
{
{
"gos_agg", new TermsAggregation("terms")
{
Field = FieldName,
Size = 100,
}
},
},
};
}
private static IDictionary<string, int> ReadAggregation(ISearchResponse<EsConcordanceDto> response)
{
var terms = response.Aggregations.Terms("gos_agg");
return terms?.Buckets?.ToDictionary(x => x.Key, x => x.DocCount.HasValue ? (int)x.DocCount.Value : 0);
}
}
}
@@ -0,0 +1,16 @@
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.Aggregations
{
public class DiscourseChannelAggregator : BaseAggregator
{
public DiscourseChannelAggregator(IElasticClient elasticClient, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
: base(elasticClient, indexProviderFactory, queryBuilderFactory)
{
}
protected override string FieldName => "discourseChannelId";
}
}
@@ -0,0 +1,16 @@
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.Aggregations
{
public class DiscourseEventAggregator : BaseAggregator
{
public DiscourseEventAggregator(IElasticClient elasticClient, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
: base(elasticClient, indexProviderFactory, queryBuilderFactory)
{
}
protected override string FieldName => "discourseEventId";
}
}
@@ -0,0 +1,17 @@
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.Aggregations
{
public class DiscourseRegionAggregator : BaseAggregator
{
public DiscourseRegionAggregator(IElasticClient elasticClient, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
: base(elasticClient, indexProviderFactory, queryBuilderFactory)
{
}
protected override string FieldName => "discourseRegionId";
}
}
@@ -0,0 +1,16 @@
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.Aggregations
{
public class DiscourseTypeAggregator : BaseAggregator
{
public DiscourseTypeAggregator(IElasticClient elasticClient, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
: base(elasticClient, indexProviderFactory, queryBuilderFactory)
{
}
protected override string FieldName => "discourseTypeId";
}
}
@@ -0,0 +1,16 @@
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.Aggregations
{
public class DiscourseYearAggregator : BaseAggregator
{
public DiscourseYearAggregator(IElasticClient elasticClient, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
: base(elasticClient, indexProviderFactory, queryBuilderFactory)
{
}
protected override string FieldName => "discourseYear";
}
}
@@ -0,0 +1,16 @@
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.Aggregations
{
public class LemmaAggregator : BaseAggregator
{
public LemmaAggregator(IElasticClient elasticClient, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
: base(elasticClient, indexProviderFactory, queryBuilderFactory)
{
}
protected override string FieldName => "token.lemma";
}
}
@@ -0,0 +1,16 @@
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.Aggregations
{
public class PartOfSpeechAggregator : BaseAggregator
{
public PartOfSpeechAggregator(IElasticClient elasticClient, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
: base(elasticClient, indexProviderFactory, queryBuilderFactory)
{
}
protected override string FieldName => "token.partOfSpeechId";
}
}
@@ -0,0 +1,17 @@
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.Aggregations
{
public class SpeakerAgeAggregator : BaseAggregator
{
public SpeakerAgeAggregator(IElasticClient elasticClient, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
: base(elasticClient, indexProviderFactory, queryBuilderFactory)
{
}
protected override string FieldName => "speakerAgeId";
}
}
@@ -0,0 +1,16 @@
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.Aggregations
{
public class SpeakerEducationAggregator : BaseAggregator
{
public SpeakerEducationAggregator(IElasticClient elasticClient, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
: base(elasticClient, indexProviderFactory, queryBuilderFactory)
{
}
protected override string FieldName => "speakerEducationId";
}
}
@@ -0,0 +1,16 @@
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.Aggregations
{
public class SpeakerLanguageAggregator : BaseAggregator
{
public SpeakerLanguageAggregator(IElasticClient elasticClient, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
: base(elasticClient, indexProviderFactory, queryBuilderFactory)
{
}
protected override string FieldName => "speakerLanguageId";
}
}
@@ -0,0 +1,17 @@
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.Aggregations
{
public class SpeakerRegionAggregator : BaseAggregator
{
public SpeakerRegionAggregator(IElasticClient elasticClient, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
: base(elasticClient, indexProviderFactory, queryBuilderFactory)
{
}
protected override string FieldName => "speakerRegionId";
}
}
@@ -0,0 +1,16 @@
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.Aggregations
{
public class SpeakerSexAggregator : BaseAggregator
{
public SpeakerSexAggregator(IElasticClient elasticClient, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
: base(elasticClient, indexProviderFactory, queryBuilderFactory)
{
}
protected override string FieldName => "speakerSexId";
}
}
@@ -0,0 +1,83 @@
using System.Linq;
using System.Threading.Tasks;
using Gos.Core.Entities;
using Gos.Core.Model;
using Gos.Infrastructure.Search.Dtos;
using Gos.Services.Services.PartOfSpeechService;
namespace Gos.Infrastructure.Search.Converters
{
public class EsConcordanceDtoConverter : IEsDtoConverter<Concordance, EsConcordanceDto>
{
private readonly IPartOfSpeechService partOfSpeechService;
public EsConcordanceDtoConverter(IPartOfSpeechService partOfSpeechService)
{
this.partOfSpeechService = partOfSpeechService;
}
public async Task<EsConcordanceDto> Convert(Concordance entity)
{
var discourse = entity.Statement.Discourse;
var speaker = entity.Statement.Speaker;
return new EsConcordanceDto
{
DiscourseId = discourse.Id,
DiscourseChannelId = discourse.Channel?.Id,
DiscourseEventId = discourse.Event?.Id,
DiscourseRegionId = discourse.Region?.Id,
DiscourseTypeId = discourse.Type?.Id,
DiscourseYear = discourse.Date.Year,
SpeakerAgeId = speaker?.Age?.Id,
SpeakerEducationId = speaker?.Education?.Id,
SpeakerLanguageId = speaker?.Language?.Id,
SpeakerRegionId = speaker?.Region1?.Id,
SpeakerSexId = speaker?.Sex?.Id,
StatementOrder = entity.Statement.Order,
Token = await ConvertToken(entity.Token),
TokenLeft1 = await ConvertToken(entity.TokenLeft1),
TokenLeft2 = await ConvertToken(entity.TokenLeft2),
TokenLeft3 = await ConvertToken(entity.TokenLeft3),
TokenLeft4 = await ConvertToken(entity.TokenLeft4),
TokenLeft5 = await ConvertToken(entity.TokenLeft5),
TokenLeft6 = await ConvertToken(entity.TokenLeft6),
TokenLeft7 = await ConvertToken(entity.TokenLeft7),
TokenLeft8 = await ConvertToken(entity.TokenLeft8),
TokenLeft9 = await ConvertToken(entity.TokenLeft9),
TokenLeft10 = await ConvertToken(entity.TokenLeft10),
TokenOrder = entity.Token.DiscourseOrder,
TokenRight1 = await ConvertToken(entity.TokenRight1),
TokenRight2 = await ConvertToken(entity.TokenRight2),
TokenRight3 = await ConvertToken(entity.TokenRight3),
TokenRight4 = await ConvertToken(entity.TokenRight4),
TokenRight5 = await ConvertToken(entity.TokenRight5),
TokenRight6 = await ConvertToken(entity.TokenRight6),
TokenRight7 = await ConvertToken(entity.TokenRight7),
TokenRight8 = await ConvertToken(entity.TokenRight8),
TokenRight9 = await ConvertToken(entity.TokenRight9),
TokenRight10 = await ConvertToken(entity.TokenRight10),
};
}
private async Task<EsTokenDto> ConvertToken(Token token)
{
if (token != null)
{
var partOfSpeech = await partOfSpeechService.GetPartOfSpeechByMsdCode(token.Msd);
return new EsTokenDto
{
Conversational = token.ConversationalForm,
ConversationalLower = token.ConversationalForm?.ToLower(),
Lemma = token.Lemma,
LemmaLower = token.Lemma?.ToLower(),
Msd = token.Msd,
PartOfSpeechId = partOfSpeech?.Id,
Standard = token.StandardForm,
StandardLower = token.StandardForm?.ToLower(),
};
}
return null;
}
}
}
@@ -0,0 +1,19 @@
using Autofac;
namespace Gos.Infrastructure.Search.Converters
{
public class EsDtoConverterFactory : IEsDtoConverterFactory
{
private readonly ILifetimeScope lifetimeScope;
public EsDtoConverterFactory(ILifetimeScope lifetimeScope)
{
this.lifetimeScope = lifetimeScope;
}
public IEsDtoConverter<TEntity, TDto> GetConverter<TEntity, TDto>()
{
return lifetimeScope.Resolve<IEsDtoConverter<TEntity, TDto>>();
}
}
}
@@ -0,0 +1,13 @@
using System.Threading.Tasks;
namespace Gos.Infrastructure.Search.Converters
{
public interface IEsDtoConverter<TEntity, TDto> : IEsDtoConverter
{
Task<TDto> Convert(TEntity entity);
}
public interface IEsDtoConverter
{
}
}
@@ -0,0 +1,7 @@
namespace Gos.Infrastructure.Search.Converters
{
public interface IEsDtoConverterFactory
{
IEsDtoConverter<TEntity, TDto> GetConverter<TEntity, TDto>();
}
}
@@ -0,0 +1,78 @@
using Nest;
namespace Gos.Infrastructure.Search.Dtos
{
public class EsConcordanceDto
{
public int? DiscourseChannelId { get; set; }
public int? DiscourseEventId { get; set; }
[Number(Store = true)]
public int DiscourseId { get; set; }
public int? DiscourseRegionId { get; set; }
public int? DiscourseTypeId { get; set; }
public int? DiscourseYear { get; set; }
public int? SpeakerAgeId { get; set; }
public int? SpeakerEducationId { get; set; }
public int? SpeakerLanguageId { get; set; }
public int? SpeakerRegionId { get; set; }
public int? SpeakerSexId { get; set; }
[Number(Store = true)]
public int StatementOrder { get; set; }
public EsTokenDto Token { get; set; }
public EsTokenDto TokenLeft1 { get; set; }
public EsTokenDto TokenLeft10 { get; set; }
public EsTokenDto TokenLeft2 { get; set; }
public EsTokenDto TokenLeft3 { get; set; }
public EsTokenDto TokenLeft4 { get; set; }
public EsTokenDto TokenLeft5 { get; set; }
public EsTokenDto TokenLeft6 { get; set; }
public EsTokenDto TokenLeft7 { get; set; }
public EsTokenDto TokenLeft8 { get; set; }
public EsTokenDto TokenLeft9 { get; set; }
[Number(Store = true)]
public int TokenOrder { get; set; }
public EsTokenDto TokenRight1 { get; set; }
public EsTokenDto TokenRight10 { get; set; }
public EsTokenDto TokenRight2 { get; set; }
public EsTokenDto TokenRight3 { get; set; }
public EsTokenDto TokenRight4 { get; set; }
public EsTokenDto TokenRight5 { get; set; }
public EsTokenDto TokenRight6 { get; set; }
public EsTokenDto TokenRight7 { get; set; }
public EsTokenDto TokenRight8 { get; set; }
public EsTokenDto TokenRight9 { get; set; }
}
}
@@ -0,0 +1,30 @@
using Nest;
namespace Gos.Infrastructure.Search.Dtos
{
public class EsTokenDto
{
[Keyword]
public string Conversational { get; set; }
[Keyword]
public string ConversationalLower { get; set; }
[Keyword]
public string Lemma { get; set; }
[Keyword]
public string LemmaLower { get; set; }
[Keyword]
public string Msd { get; set; }
public int? PartOfSpeechId { get; set; }
[Keyword]
public string Standard { get; set; }
[Keyword]
public string StandardLower { get; set; }
}
}
@@ -0,0 +1,29 @@
using System;
using Gos.Core;
using Microsoft.Extensions.Configuration;
using Nest;
namespace Gos.Infrastructure.Search
{
public class ElasticClientFactory
{
private readonly IConfiguration configuration;
public ElasticClientFactory(IConfiguration configuration)
{
this.configuration = configuration;
}
public IElasticClient CreateClient()
{
var connectionString = configuration[ConfigurationKey.Elastic.ConnectionString];
var connectionSettings = new ConnectionSettings(new Uri(connectionString)).SniffOnStartup(false).RequestTimeout(TimeSpan.FromMinutes(5));
#if DEBUG
connectionSettings.EnableDebugMode().IncludeServerStackTraceOnError(false);
#endif
return new ElasticClient(connectionSettings);
}
}
}
@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Gos.Core.Search;
using Gos.Core.Search.Queries;
using Gos.Infrastructure.Search.Converters;
using Gos.Infrastructure.Search.Dtos;
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryHandlers;
using Nest;
namespace Gos.Infrastructure.Search
{
public class ElasticSearchEngine : ISearchEngine
{
private readonly IElasticClient client;
private readonly IEsDtoConverterFactory esDtoConverterFactory;
private readonly IIndexProviderFactory indexProviderFactory;
private readonly IQueryHandlerFactory queryHandlerFactory;
public ElasticSearchEngine(
IElasticClient client,
IEsDtoConverterFactory esDtoConverterFactory,
IIndexProviderFactory indexProviderFactory,
IQueryHandlerFactory queryHandlerFactory)
{
this.client = client;
this.esDtoConverterFactory = esDtoConverterFactory;
this.indexProviderFactory = indexProviderFactory;
this.queryHandlerFactory = queryHandlerFactory;
}
public async Task Commit()
{
foreach (var indexProvider in indexProviderFactory.GetAllProviders())
{
if (await indexProvider.IndexExists())
{
await indexProvider.RefreshIndex();
}
}
}
public async Task CreateSchema()
{
foreach (var indexProvider in indexProviderFactory.GetAllProviders())
{
if (!await indexProvider.IndexExists())
{
await indexProvider.CreateIndex();
}
}
}
public async Task DeleteSchema()
{
foreach (var indexProvider in indexProviderFactory.GetAllProviders())
{
if (await indexProvider.IndexExists())
{
await indexProvider.DeleteIndex();
}
}
}
public async Task Index<TEntity>(IEnumerable<TEntity> entities)
where TEntity : class
{
var indexProvider = indexProviderFactory.GetProvider<EsConcordanceDto>();
var indexName = indexProvider.IndexName;
var request = new BulkRequest(indexName)
{
Operations = new List<IBulkOperation>(),
Timeout = TimeSpan.FromMinutes(5)
};
// Get converter and convert entities to dtos
var converter = esDtoConverterFactory.GetConverter<TEntity, EsConcordanceDto>();
foreach (var entity in entities)
{
var dto = await converter.Convert(entity);
request.Operations.Add(new BulkIndexOperation<EsConcordanceDto>(dto));
}
var response = await client.BulkAsync(request);
if (!response.IsValid)
{
throw new Exception($"Invalid response from Elastic: {response.DebugInformation}!");
}
}
public TResult Search<TQuery, TResult>(TQuery query)
where TQuery : Query
where TResult : QueryResult
{
// Get query handler
var queryHandler = queryHandlerFactory.Get<TQuery, TResult>();
return queryHandler.Handle(query);
}
}
}
@@ -0,0 +1,62 @@
using System;
using System.Threading.Tasks;
using Nest;
namespace Gos.Infrastructure.Search.Indexes
{
public abstract class BaseIndexProvider<TEntity> : IIndexProvider<TEntity>
where TEntity : class
{
private readonly IElasticClient client;
protected BaseIndexProvider(IElasticClient client)
{
this.client = client;
}
public virtual string IndexName => $"gos_{typeof(TEntity).Name.ToLower()}";
public async Task CreateIndex()
{
var response = await client.Indices.CreateAsync(
IndexName,
c => c.Settings(
s => s.Setting("max_result_window", int.MaxValue)
.NumberOfShards(5)
.NumberOfReplicas(0)
.RefreshInterval(-1)
.Merge(ms => ms.Scheduler(ss => ss.MaxThreadCount(1))))
.Map(ms => ms.AutoMap<TEntity>().SourceField(s => s.Enabled(false))));
if (!response.IsValid)
{
throw new Exception($"Invalid Elastic response: {response.DebugInformation}!");
}
}
public async Task DeleteIndex()
{
var response = await client.Indices.DeleteAsync(IndexName);
if (!response.IsValid)
{
throw new Exception($"Invalid Elastic response: {response.DebugInformation}!");
}
}
public async Task<bool> IndexExists()
{
return (await client.Indices.ExistsAsync(IndexName)).Exists;
}
public async Task RefreshIndex()
{
var response = await client.Indices.RefreshAsync(IndexName);
if (!response.IsValid)
{
throw new Exception($"Invalid Elastic response: {response.DebugInformation}!");
}
}
}
}
@@ -0,0 +1,13 @@
using Gos.Infrastructure.Search.Dtos;
using Nest;
namespace Gos.Infrastructure.Search.Indexes
{
public class ConcordanceIndexProvider : BaseIndexProvider<EsConcordanceDto>
{
public ConcordanceIndexProvider(IElasticClient client)
: base(client)
{
}
}
}
@@ -0,0 +1,21 @@
using System.Threading.Tasks;
namespace Gos.Infrastructure.Search.Indexes
{
public interface IIndexProvider<TDocument> : IIndexProvider
{
}
public interface IIndexProvider
{
string IndexName { get; }
Task CreateIndex();
Task DeleteIndex();
Task<bool> IndexExists();
Task RefreshIndex();
}
}
@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Gos.Infrastructure.Search.Indexes
{
public interface IIndexProviderFactory
{
IEnumerable<IIndexProvider> GetAllProviders();
IIndexProvider<TDocument> GetProvider<TDocument>();
}
}
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using Autofac;
namespace Gos.Infrastructure.Search.Indexes
{
public class IndexProviderFactory : IIndexProviderFactory
{
private readonly ILifetimeScope lifetimeScope;
public IndexProviderFactory(ILifetimeScope lifetimeScope)
{
this.lifetimeScope = lifetimeScope;
}
public IEnumerable<IIndexProvider> GetAllProviders()
{
return lifetimeScope.Resolve<IEnumerable<IIndexProvider>>();
}
public IIndexProvider<TDocument> GetProvider<TDocument>()
{
return lifetimeScope.Resolve<IIndexProvider<TDocument>>();
}
}
}
@@ -0,0 +1,118 @@
using System.Collections.Generic;
using System.Linq;
using Gos.Core.Extensions;
using Gos.Core.Search.Queries;
using Nest;
namespace Gos.Infrastructure.Search.QueryBuilders
{
public abstract class BaseQueryBuilder
{
protected static List<QueryContainer> GetFilterQueries(Query query)
{
var queries = new List<QueryContainer>();
if (!query.DiscourseTypeIds.IsNullOrEmpty())
{
queries.Add(
new TermsQuery()
{
Field = "discourseTypeId",
Terms = query.DiscourseTypeIds.Cast<object>(),
});
}
if (!query.DiscourseChannelIds.IsNullOrEmpty())
{
queries.Add(
new TermsQuery()
{
Field = "discourseChannelId",
Terms = query.DiscourseChannelIds.Cast<object>(),
});
}
if (!query.DiscourseEventIds.IsNullOrEmpty())
{
queries.Add(
new TermsQuery()
{
Field = "discourseEventId",
Terms = query.DiscourseEventIds.Cast<object>(),
});
}
if (!query.DiscourseRegionIds.IsNullOrEmpty())
{
queries.Add(
new TermsQuery()
{
Field = "discourseRegionId",
Terms = query.DiscourseRegionIds.Cast<object>(),
});
}
if (!query.DiscourseYears.IsNullOrEmpty())
{
queries.Add(
new TermsQuery()
{
Field = "discourseYear",
Terms = query.DiscourseYears.Cast<object>(),
});
}
if (!query.SpeakerAgeIds.IsNullOrEmpty())
{
queries.Add(
new TermsQuery()
{
Field = "speakerAgeId",
Terms = query.SpeakerAgeIds.Cast<object>(),
});
}
if (!query.SpeakerEducationIds.IsNullOrEmpty())
{
queries.Add(
new TermsQuery()
{
Field = "speakerEducationId",
Terms = query.SpeakerEducationIds.Cast<object>(),
});
}
if (!query.SpeakerLanguageIds.IsNullOrEmpty())
{
queries.Add(
new TermsQuery()
{
Field = "speakerLanguageId",
Terms = query.SpeakerLanguageIds.Cast<object>(),
});
}
if (!query.SpeakerRegionIds.IsNullOrEmpty())
{
queries.Add(
new TermsQuery()
{
Field = "speakerRegionId",
Terms = query.SpeakerRegionIds.Cast<object>(),
});
}
if (!query.SpeakerSexIds.IsNullOrEmpty())
{
queries.Add(
new TermsQuery()
{
Field = "speakerSexId",
Terms = query.SpeakerSexIds.Cast<object>(),
});
}
return queries;
}
}
}
@@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Gos.Core.Extensions;
using Gos.Core.Search.Queries.Concordance;
using Gos.Infrastructure.Extensions;
using Gos.ServiceModel.Enums;
using Nest;
using ConditionType = Gos.ServiceModel.Enums.ConditionType;
namespace Gos.Infrastructure.Search.QueryBuilders
{
public class ConcordanceQueryBuilder : BaseQueryBuilder, IQueryBuilder<ConcordanceQuery>
{
public QueryContainer Build(ConcordanceQuery query)
{
// Get filter queries
var queries = GetFilterQueries(query);
// Main word queries
queries.Add(GetMainWordQuery(query.MainWord));
// Words in context queries
if (!query.WordsInContext.IsNullOrEmpty())
{
queries.AddRange(query.WordsInContext.Select(GetWordInContextQuery));
}
// Merge queries
var mergedQuery = queries.ToBooleanAndQuery();
// Check if we should return only random rows (used in export)
if (query.ReturnRandomRows)
{
mergedQuery = new FunctionScoreQuery()
{
Query = mergedQuery,
Functions = new List<IScoreFunction>()
{
new RandomScoreFunction()
{
Seed = DateTime.Now.Ticks,
},
},
};
}
return mergedQuery;
}
private QueryContainer GetMainWordQuery(ConcordanceQueryMainWord word)
{
return GetWordQuery(word, GetTokenField(0));
}
private QueryContainer GetWordInContextQuery(ConcordanceQueryWordInContext word)
{
// Get word positions
var positions = GetPositions(word);
var queries = new List<QueryContainer>();
foreach (var position in positions)
{
var positionQuery = GetWordQuery(word, GetTokenField(position));
queries.Add(positionQuery);
}
var query = queries.ToBooleanOrQuery();
return word.Condition == ConditionType.Is ? query : !query;
}
private QueryContainer GetWordQuery(ConcordanceQueryWord word, string tokenField)
{
var queries = new List<QueryContainer>();
if (!string.IsNullOrEmpty(word.ConversationalForm))
{
queries.Add(
new TermQuery
{
Field = $"{tokenField}.conversationalLower",
Value = word.ConversationalForm.ToLower(),
});
}
if (!string.IsNullOrEmpty(word.StandardForm))
{
queries.Add(
new TermQuery
{
Field = $"{tokenField}.standardLower",
Value = word.StandardForm.ToLower(),
});
}
if (!word.Lemmas.IsNullOrEmpty())
{
var lowercasedLemmas = word.Lemmas.Select(l => l.ToLower());
queries.Add(
new TermsQuery()
{
Field = $"{tokenField}.lemmaLower",
Terms = lowercasedLemmas,
});
}
// If no criteria was specified, return MatchNoneQuery
if (queries.Count == 0)
{
return new MatchNoneQuery();
}
if (word.PartOfSpeechId.HasValue || !word.Msds.IsNullOrEmpty())
{
QueryContainer partOfSpeechQuery;
if (!word.Msds.IsNullOrEmpty())
{
partOfSpeechQuery = new TermsQuery()
{
Field = $"{tokenField}.msd",
Terms = word.Msds,
};
}
else
{
partOfSpeechQuery = new TermQuery()
{
Field = $"{tokenField}.partOfSpeechId",
Value = word.PartOfSpeechId.Value,
};
}
if (word.PartOfSpeechCondition == ConditionType.IsNot)
{
partOfSpeechQuery = !partOfSpeechQuery;
}
queries.Add(partOfSpeechQuery);
}
return queries.ToBooleanAndQuery();
}
private static List<int> GetPositions(ConcordanceQueryWordInContext word)
{
var positions = new List<int>();
AddPositions(word.LeftPosition, word.DistanceType, true);
AddPositions(word.RightPosition, word.DistanceType, false);
return positions;
void AddPositions(int position, DistanceType distanceType, bool negative)
{
if (position != 0)
{
if (distanceType == DistanceType.Position)
{
positions.Add(negative ? -position : position);
}
else
{
for (var i = 1; i <= position; i++)
{
positions.Add(negative ? -i : i);
}
}
}
}
}
private static string GetTokenField(int position)
{
return position switch
{
< 0 => $"tokenLeft{-position}",
> 0 => $"tokenRight{position}",
_ => "token",
};
}
}
}
@@ -0,0 +1,11 @@
using Gos.Core.Search.Queries;
using Nest;
namespace Gos.Infrastructure.Search.QueryBuilders
{
public interface IQueryBuilder<TQuery>
where TQuery : Query
{
QueryContainer Build(TQuery query);
}
}
@@ -0,0 +1,10 @@
using Gos.Core.Search.Queries;
namespace Gos.Infrastructure.Search.QueryBuilders
{
public interface IQueryBuilderFactory
{
IQueryBuilder<TQuery> GetBuilder<TQuery>()
where TQuery : Query;
}
}
@@ -0,0 +1,124 @@
using System;
using System.Linq;
using Gos.Core.Extensions;
using Gos.Core.Search.Queries.List;
using Gos.Infrastructure.Extensions;
using Gos.ServiceModel.Enums;
using Nest;
using ConditionType = Gos.ServiceModel.Enums.ConditionType;
namespace Gos.Infrastructure.Search.QueryBuilders
{
public class ListQueryBuilder : BaseQueryBuilder, IQueryBuilder<ListQuery>
{
public QueryContainer Build(ListQuery query)
{
// Get filter queries
var queries = GetFilterQueries(query);
// Append main queries
switch (query.TranscriptionType)
{
case TranscriptionType.Conversational:
queries.Add(
new WildcardQuery()
{
Field = "token.conversationalLower",
Value = query.Query.ToLower(),
});
break;
case TranscriptionType.Standard:
if (query.Query.StartsWith("\"") && query.Query.EndsWith("\""))
{
var form = query.Query.Substring(1, query.Query.Length - 2);
queries.Add(
new WildcardQuery()
{
Field = "token.standardLower",
Value = form.ToLower(),
});
}
else
{
queries.Add(
new WildcardQuery()
{
Field = "token.lemmaLower",
Value = query.Query.ToLower(),
});
}
break;
default:
throw new Exception($"Invalid TranscriptionType: {query.TranscriptionType.ToString()}!");
}
// Filter by conversational or standard form
if (query.GroupByMsd)
{
if (!string.IsNullOrEmpty(query.ConversationalForm))
{
queries.Add(
new TermQuery()
{
Field = "token.conversational",
Value = query.ConversationalForm,
});
}
if (!string.IsNullOrEmpty(query.StandardForm))
{
queries.Add(
new TermQuery()
{
Field = "token.standard",
Value = query.StandardForm,
});
}
}
// Filter by lemma
if (!query.Lemmas.IsNullOrEmpty())
{
queries.Add(
new TermsQuery()
{
Field = "token.lemma",
Terms = query.Lemmas,
});
}
// Filter by part of speech
if (!query.PartOfSpeechIds.IsNullOrEmpty() || !query.Msds.IsNullOrEmpty())
{
QueryContainer partOfSpeechQuery;
if (!query.Msds.IsNullOrEmpty())
{
partOfSpeechQuery = new TermsQuery()
{
Field = "token.msd",
Terms = query.Msds,
};
}
else
{
partOfSpeechQuery = new TermsQuery()
{
Field = "token.partOfSpeechId",
Terms = query.PartOfSpeechIds.Cast<object>(),
};
}
if (query.Condition == ConditionType.IsNot)
{
partOfSpeechQuery = !partOfSpeechQuery;
}
queries.Add(partOfSpeechQuery);
}
// Return query
return queries.ToBooleanAndQuery();
}
}
}
@@ -0,0 +1,21 @@
using Autofac;
using Gos.Core.Search.Queries;
namespace Gos.Infrastructure.Search.QueryBuilders
{
public class QueryBuilderFactory : IQueryBuilderFactory
{
private readonly ILifetimeScope lifetimeScope;
public QueryBuilderFactory(ILifetimeScope lifetimeScope)
{
this.lifetimeScope = lifetimeScope;
}
public IQueryBuilder<TQuery> GetBuilder<TQuery>()
where TQuery : Query
{
return lifetimeScope.Resolve<IQueryBuilder<TQuery>>();
}
}
}
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Gos.Core.Search.Queries.Concordance;
using Gos.Infrastructure.Search.Dtos;
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.QueryHandlers
{
public class ConcordanceQueryHandler : IQueryHandler<ConcordanceQuery, ConcordanceQueryResult>
{
private readonly IElasticClient client;
private readonly IIndexProviderFactory indexProviderFactory;
private readonly IQueryBuilderFactory queryBuilderFactory;
public ConcordanceQueryHandler(IElasticClient client, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
{
this.client = client;
this.indexProviderFactory = indexProviderFactory;
this.queryBuilderFactory = queryBuilderFactory;
}
public ConcordanceQueryResult Handle(ConcordanceQuery query)
{
// Get request
var request = GetRequest(query);
// Execute search
var response = client.Search<EsConcordanceDto>(request);
if (!response.IsValid)
{
throw new Exception($"Invalid response from Elastic: {response.DebugInformation}!");
}
return new ConcordanceQueryResult()
{
Total = response.Total,
Items = GetItems(response),
};
}
private SearchRequest GetRequest(ConcordanceQuery query)
{
// Get criteria query
var queryBuilder = queryBuilderFactory.GetBuilder<ConcordanceQuery>();
var criteriaQuery = queryBuilder.Build(query);
var indexName = indexProviderFactory.GetProvider<EsConcordanceDto>().IndexName;
return new SearchRequest(indexName)
{
From = query.From,
Size = query.Size,
TrackTotalHits = true,
Query = criteriaQuery,
StoredFields = new[]
{
"discourseId",
"statementOrder",
"tokenOrder"
}
};
}
private List<ConcordanceQueryResultItem> GetItems(ISearchResponse<EsConcordanceDto> response)
{
return response.Hits.Select(
h => new ConcordanceQueryResultItem
{
DiscourseId = h.Fields.Value<int>("discourseId"),
StatementOrder = h.Fields.Value<int>("statementOrder"),
TokenOrder = h.Fields.Value<int>("tokenOrder")
})
.ToList();
}
}
}
@@ -0,0 +1,11 @@
using Gos.Core.Search.Queries;
namespace Gos.Infrastructure.Search.QueryHandlers
{
public interface IQueryHandler<TQuery, TResult>
where TQuery : Query
where TResult : QueryResult
{
TResult Handle(TQuery query);
}
}
@@ -0,0 +1,11 @@
using Gos.Core.Search.Queries;
namespace Gos.Infrastructure.Search.QueryHandlers
{
public interface IQueryHandlerFactory
{
IQueryHandler<TQuery, TResult> Get<TQuery, TResult>()
where TQuery : Query
where TResult : QueryResult;
}
}
@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Gos.Core.Search.Queries.List;
using Gos.Infrastructure.Search.Dtos;
using Gos.Infrastructure.Search.Indexes;
using Gos.Infrastructure.Search.QueryBuilders;
using Nest;
namespace Gos.Infrastructure.Search.QueryHandlers
{
public class ListQueryHandler : IQueryHandler<ListQuery, ListQueryResult>
{
private readonly IElasticClient client;
private readonly IIndexProviderFactory indexProviderFactory;
private readonly IQueryBuilderFactory queryBuilderFactory;
public ListQueryHandler(IElasticClient client, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
{
this.client = client;
this.indexProviderFactory = indexProviderFactory;
this.queryBuilderFactory = queryBuilderFactory;
}
public ListQueryResult Handle(ListQuery query)
{
// Get criteria query
var queryBuilder = queryBuilderFactory.GetBuilder<ListQuery>();
var criteriaQuery = queryBuilder.Build(query);
var items = new List<ListQueryResultItem>();
CompositeKey afterKey = null;
do
{
// Get request and execute search
var request = GetRequest(criteriaQuery, query.GroupByMsd, afterKey);
var response = client.Search<EsConcordanceDto>(request);
if (!response.IsValid)
{
throw new Exception($"Invalid response from Elastic: {response.DebugInformation}!");
}
if (response.Aggregations.ContainsKey("composite"))
{
var composite = response.Aggregations.Composite("composite");
foreach (var bucket in composite.Buckets)
{
var values = bucket.Key.Values.ToArray();
var resultItem = new ListQueryResultItem
{
ConversationalForm = values[0].ToString(),
StandardForm = values[1].ToString(),
Frequency = (int)bucket.DocCount.Value,
};
if (query.GroupByMsd)
{
resultItem.Msd = values[2].ToString();
}
items.Add(resultItem);
}
// Exit if there is nothing more to read
if (composite.Buckets.Count < 1000)
{
break;
}
afterKey = composite.AfterKey;
}
}
while (true);
return new ListQueryResult()
{
Items = items,
Total = items.Count,
};
}
private SearchRequest GetRequest(QueryContainer criteriaQuery, bool groupByMsd, CompositeKey afterKey)
{
// Get sources for aggregations
var sources = new List<ICompositeAggregationSource>
{
new TermsCompositeAggregationSource("conversational")
{
Field = "token.conversational",
},
new TermsCompositeAggregationSource("standard")
{
Field = "token.standard",
}
};
if (groupByMsd)
{
sources.Add(
new TermsCompositeAggregationSource("msd")
{
Field = "token.msd",
});
}
// Get index name
var indexName = indexProviderFactory.GetProvider<EsConcordanceDto>().IndexName;
// Compose request
return new SearchRequest(indexName)
{
From = 0,
Size = 0,
TrackTotalHits = true,
Query = criteriaQuery,
Aggregations = new AggregationDictionary()
{
{
"composite", new CompositeAggregation("composite")
{
Sources = sources,
Size = 1000,
After = afterKey,
}
}
}
};
}
}
}
@@ -0,0 +1,22 @@
using Autofac;
using Gos.Core.Search.Queries;
namespace Gos.Infrastructure.Search.QueryHandlers
{
public class QueryHandlerFactory : IQueryHandlerFactory
{
private readonly ILifetimeScope lifetimeScope;
public QueryHandlerFactory(ILifetimeScope lifetimeScope)
{
this.lifetimeScope = lifetimeScope;
}
public IQueryHandler<TQuery, TResult> Get<TQuery, TResult>()
where TQuery : Query
where TResult : QueryResult
{
return lifetimeScope.Resolve<IQueryHandler<TQuery, TResult>>();
}
}
}
@@ -0,0 +1,13 @@
using System;
using Gos.Core.Interfaces;
namespace Gos.Infrastructure.Sessions
{
public class DefaultSessionIdResolver : ISessionIdResolver
{
public string Resolve()
{
return Guid.NewGuid().ToString();
}
}
}
@@ -0,0 +1,13 @@
using System;
using Gos.Core.Interfaces;
namespace Gos.Infrastructure.Sessions
{
public class DefaultTraceIdentifierResolver : ITraceIdentifierResolver
{
public string Resolve()
{
return Guid.NewGuid().ToString();
}
}
}