/// <summary> /// Swaps alias for culture /// </summary> /// <param name="language"></param> /// <param name="oldAlias"></param> /// <param name="newAlias"></param> public void SwitchAlias(CultureInfo language, string oldAlias, string newAlias) { lock (this) { var cultureInfo = language ?? CultureInfo.CurrentUICulture; VulcanHelper.GuardForNullAlias(ref oldAlias); VulcanHelper.GuardForNullAlias(ref newAlias); var client = CreateElasticClient(CommonConnectionSettings.ConnectionSettings); // use a raw elasticclient because we just need this to be quick var oldFullAlias = VulcanHelper.GetAliasName(Index, cultureInfo, oldAlias); var newFullAlias = VulcanHelper.GetAliasName(Index, cultureInfo, newAlias); var oldIndex = client.GetAlias(a => a.Name(oldFullAlias)).Indices?.First().Key; var newIndex = client.GetAlias(a => a.Name(newFullAlias)).Indices?.First().Key; client.Alias(bad => bad.Remove(a => a.Alias(oldFullAlias).Index("*")) .Remove(a => a.Alias(newFullAlias).Index("*")) .Add(a => a.Alias(oldFullAlias).Index(newIndex)) .Add(a => a.Alias(newFullAlias).Index(oldIndex))); client.Refresh("*"); Clients?.Clear(); // force a client refresh } }
/// <summary> /// Search for content /// </summary> /// <typeparam name="T"></typeparam> /// <param name="searchDescriptor"></param> /// <param name="includeNeutralLanguage"></param> /// <param name="rootReferences"></param> /// <param name="typeFilter"></param> /// <param name="principleReadFilter"></param> /// <returns></returns> public virtual ISearchResponse <IContent> SearchContent <T>( Func <SearchDescriptor <T>, SearchDescriptor <T> > searchDescriptor = null, bool includeNeutralLanguage = false, IEnumerable <ContentReference> rootReferences = null, IEnumerable <Type> typeFilter = null, IPrincipal principleReadFilter = null) where T : class, IContent { var resolvedDescriptor = searchDescriptor == null ? new SearchDescriptor <T>() : searchDescriptor.Invoke(new SearchDescriptor <T>()); typeFilter = typeFilter ?? typeof(T).GetSearchTypesFor(VulcanFieldConstants.AbstractFilter); resolvedDescriptor = resolvedDescriptor.Type(string.Join(",", typeFilter.Select(t => t.FullName))) .ConcreteTypeSelector((d, docType) => typeof(VulcanContentHit)); var indexName = IndexName; if (!Language.Equals(CultureInfo.InvariantCulture) && includeNeutralLanguage) { indexName += "," + VulcanHelper.GetAliasName(VulcanHandler.Index, CultureInfo.InvariantCulture, IndexAlias); } resolvedDescriptor = resolvedDescriptor.Index(indexName); var validRootReferences = rootReferences?.Where(x => !ContentReference.IsNullOrEmpty(x)).ToList(); var filters = new List <QueryContainer>(); if (validRootReferences?.Count > 0) { var scopeDescriptor = new QueryContainerDescriptor <T>(). Terms(t => t.Field(VulcanFieldConstants.Ancestors).Terms(validRootReferences.Select(x => x.ToReferenceWithoutVersion().ToString()))); filters.Add(scopeDescriptor); } if (principleReadFilter != null) { var permissionDescriptor = new QueryContainerDescriptor <T>(). Terms(t => t.Field(VulcanFieldConstants.ReadPermission).Terms(principleReadFilter.GetRoles())); filters.Add(permissionDescriptor); } if (filters.Count > 0) { var descriptor = resolvedDescriptor; Func <SearchDescriptor <T>, ISearchRequest> selector = ts => descriptor; var container = selector.Invoke(new SearchDescriptor <T>()); if (container.Query != null) { filters.Insert(0, container.Query); } resolvedDescriptor = resolvedDescriptor.Query(q => q.Bool(b => b.Must(filters.ToArray()))); } var response = Search <T, IContent>(resolvedDescriptor); return(response); }
/// <summary> /// DI Constructor /// </summary> /// <param name="index"></param> /// <param name="indexAlias"></param> /// <param name="settings"></param> /// <param name="language"></param> /// <param name="contentLoader"></param> /// <param name="vulcanHandler"></param> /// <param name="vulcanPipelineSelector"></param> public VulcanClient ( string index, string indexAlias, IConnectionSettingsValues settings, CultureInfo language, IContentLoader contentLoader, IVulcanHandler vulcanHandler, IVulcanPipelineSelector vulcanPipelineSelector) : base(settings) { Language = language ?? throw new Exception("Vulcan client requires a language (you may use CultureInfo.InvariantCulture if needed for non-language specific data)"); IndexName = VulcanHelper.GetAliasName(index, language, indexAlias); IndexAlias = indexAlias; ContentLoader = contentLoader; VulcanHandler = vulcanHandler; _vulcanPipelineSelector = vulcanPipelineSelector; }
/// <summary> /// Get a Vulcan client /// </summary> /// <param name="language">Pass in null for current culture, a specific culture or CultureInfo.InvariantCulture to get a client for non-language specific data</param> /// <param name="alias"></param> /// <returns>A Vulcan client</returns> public virtual IVulcanClient GetClient(CultureInfo language = null, string alias = null) { var cultureInfo = language ?? CultureInfo.CurrentUICulture; var aliasSafe = string.IsNullOrWhiteSpace(alias) ? VulcanHelper.MasterAlias : alias; IVulcanClient storedClient; lock (_lockObject) { if (!Clients.ContainsKey(aliasSafe)) { Clients[aliasSafe] = new ConcurrentDictionary <CultureInfo, IVulcanClient>(); } if (Clients[aliasSafe].TryGetValue(cultureInfo, out storedClient)) { return(storedClient); } // todo: need some sort of check here to make sure we still need to create a client var aliasName = VulcanHelper.GetAliasName(Index, cultureInfo, alias); var settings = CommonConnectionSettings.ConnectionSettings; settings.InferMappingFor <ContentMixin>(pd => pd.Ignore(p => p.MixinInstance)); settings.DefaultIndex(aliasName); var client = CreateVulcanClient(Index, alias, settings, cultureInfo); var nodesInfo = client.NodesInfo(); // first let's check our version if (nodesInfo?.Nodes?.Any() != true) { throw new Exception("Could not get Nodes info to check Elasticsearch Version. Check that you are correctly connected to Elasticsearch?"); } var node = nodesInfo.Nodes.First(); // just use first if (string.IsNullOrWhiteSpace(node.Value.Version)) // just use first { throw new Exception("Could not find a version on node to check Elasticsearch Version. Check that you are correctly connected to Elasticsearch?"); } if (node.Value.Version.StartsWith("1.")) { throw new Exception("Sorry, Vulcan only works with Elasticsearch version 2.x or higher. The Elasticsearch node you are currently connected to is version " + node.Value.Version); } client.RunCustomIndexTemplates(Index, Logger); // keep our base last with lowest possible Order #if NEST2 client.PutIndexTemplate($"{Index}_analyzer_disabling", ad => ad .Order(0) .Template($"{Index}*") //match on all created indices for index name .Mappings(mappings => mappings.Map("_default_", map => map.DynamicTemplates( dyn => dyn.DynamicTemplate("analyzer_template", dt => dt .Match("*") //matches all fields .MatchMappingType("string") //that are a string .Mapping(dynmap => dynmap.String(s => s .NotAnalyzed() .IgnoreAbove(CreateIndexCustomizer.IgnoreAbove) // needed for: document contains at least one immense term in field .IncludeInAll(false) .Fields(f => f .String(ana => ana .Name(VulcanFieldConstants.AnalyzedModifier) .IncludeInAll(false) .Store() ) )) ) ))))); #elif NEST5 // note: strings are no more in ES5, for not analyzed text use Keyword and for analyzed use Text client.PutIndexTemplate($"{Index}_analyzer_disabling", ad => ad .Order(0) .Template($"{Index}*") //match on all created indices for index name .Mappings(mappings => mappings.Map("_default_", map => map.DynamicTemplates( dyn => dyn.DynamicTemplate("analyzer_template", dt => dt .Match("*") //matches all fields .MatchMappingType("string") //that are a string .Mapping(dynmap => dynmap.Keyword(s => s .IgnoreAbove(CreateIndexCustomizer.IgnoreAbove) // needed for: document contains at least one immense term in field .Fields(f => f .Text(ana => ana .Name(VulcanFieldConstants.AnalyzedModifier) .Store() ) )) ) ))))); #endif string actualIndexName = null; if (client.AliasExists(a => a.Name(aliasName)).Exists) { var indices = client.GetAlias(a => a.Name(aliasName)).Indices; if (indices != null) { if (indices.Any()) { actualIndexName = indices.First().Key; } } } if (actualIndexName == null) { actualIndexName = VulcanHelper.GetRawIndexName(Index, cultureInfo); var response = client.CreateIndex(actualIndexName, CreateIndexCustomizer.CustomizeIndex); if (!response.IsValid) { Logger.Error("Could not create index " + actualIndexName + ": " + response.DebugInformation); } else { // set up the indexAlias client.PutAlias(actualIndexName, aliasName); } } client.Refresh(actualIndexName); var closeResponse = client.CloseIndex(actualIndexName); if (!closeResponse.IsValid) { Logger.Error("Could not close index " + actualIndexName + ": " + closeResponse.DebugInformation); } InitializeAnalyzer(client); // run installers foreach (var installer in _vulcanPipelineInstallers) { installer.Install(client); } // allows for customizations client.RunCustomizers(Logger); client.RunCustomMappers(Logger); client.OpenIndex(actualIndexName); if (CreateIndexCustomizer.WaitForActiveShards > 0) { // Init shards to attempt to fix empty results on first request client.ClusterHealth(x => x.WaitForActiveShards( #if NEST2 CreateIndexCustomizer.WaitForActiveShards #elif NEST5 CreateIndexCustomizer.WaitForActiveShards.ToString() #endif )); } storedClient = client; } // ReSharper disable once InconsistentlySynchronizedField Clients[aliasSafe][cultureInfo] = storedClient; return(storedClient); }