001/*- 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.search.elastic; 021 022import ca.uhn.fhir.context.ConfigurationException; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers; 025import org.apache.commons.lang3.StringUtils; 026import org.hibernate.search.backend.elasticsearch.aws.cfg.ElasticsearchAwsBackendSettings; 027import org.hibernate.search.backend.elasticsearch.aws.cfg.ElasticsearchAwsCredentialsTypeNames; 028import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchBackendSettings; 029import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchIndexSettings; 030import org.hibernate.search.backend.elasticsearch.index.IndexStatus; 031import org.hibernate.search.engine.cfg.BackendSettings; 032import org.hibernate.search.mapper.orm.automaticindexing.session.AutomaticIndexingSynchronizationStrategyNames; 033import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings; 034import org.hibernate.search.mapper.orm.schema.management.SchemaManagementStrategyName; 035import org.slf4j.Logger; 036 037import java.util.Properties; 038 039import static org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchBackendSettings.Defaults.SCROLL_TIMEOUT; 040import static org.slf4j.LoggerFactory.getLogger; 041 042/** 043 * This class is used to inject appropriate properties into a hibernate 044 * Properties object being used to create an entitymanager for a HAPI 045 * FHIR JPA server. This class also injects a starter template into the ES cluster. 046 */ 047public class ElasticsearchHibernatePropertiesBuilder { 048 private static final Logger ourLog = getLogger(ElasticsearchHibernatePropertiesBuilder.class); 049 050 private IndexStatus myRequiredIndexStatus = IndexStatus.YELLOW; 051 private SchemaManagementStrategyName myIndexSchemaManagementStrategy = SchemaManagementStrategyName.CREATE; 052 053 private String myHosts; 054 private String myUsername; 055 private String myPassword; 056 057 public String getAwsRegion() { 058 return myAwsRegion; 059 } 060 061 private String myAwsRegion; 062 private long myIndexManagementWaitTimeoutMillis = 10000L; 063 private long myScrollTimeoutSecs = SCROLL_TIMEOUT; 064 private String myDebugSyncStrategy = AutomaticIndexingSynchronizationStrategyNames.ASYNC; 065 private boolean myDebugPrettyPrintJsonLog = false; 066 private String myProtocol; 067 068 public ElasticsearchHibernatePropertiesBuilder setUsername(String theUsername) { 069 myUsername = theUsername; 070 return this; 071 } 072 073 public ElasticsearchHibernatePropertiesBuilder setPassword(String thePassword) { 074 myPassword = thePassword; 075 return this; 076 } 077 078 public void apply(Properties theProperties) { 079 080 // the below properties are used for ElasticSearch integration 081 theProperties.put(BackendSettings.backendKey(BackendSettings.TYPE), "elasticsearch"); 082 theProperties.put( 083 BackendSettings.backendKey(ElasticsearchIndexSettings.ANALYSIS_CONFIGURER), 084 HapiHSearchAnalysisConfigurers.HapiElasticsearchAnalysisConfigurer.class.getName()); 085 theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.HOSTS), myHosts); 086 theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.PROTOCOL), myProtocol); 087 088 if (StringUtils.isNotBlank(myUsername)) { 089 theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.USERNAME), myUsername); 090 } 091 if (StringUtils.isNotBlank(myPassword)) { 092 theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.PASSWORD), myPassword); 093 } 094 theProperties.put( 095 HibernateOrmMapperSettings.SCHEMA_MANAGEMENT_STRATEGY, 096 myIndexSchemaManagementStrategy.externalRepresentation()); 097 theProperties.put( 098 BackendSettings.backendKey( 099 ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_MINIMAL_REQUIRED_STATUS_WAIT_TIMEOUT), 100 Long.toString(myIndexManagementWaitTimeoutMillis)); 101 theProperties.put( 102 BackendSettings.backendKey(ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_MINIMAL_REQUIRED_STATUS), 103 myRequiredIndexStatus.externalRepresentation()); 104 // Need the mapping to be dynamic because of terminology indexes. 105 theProperties.put(BackendSettings.backendKey(ElasticsearchIndexSettings.DYNAMIC_MAPPING), "true"); 106 // Only for unit tests 107 theProperties.put(HibernateOrmMapperSettings.AUTOMATIC_INDEXING_SYNCHRONIZATION_STRATEGY, myDebugSyncStrategy); 108 theProperties.put( 109 BackendSettings.backendKey(ElasticsearchBackendSettings.LOG_JSON_PRETTY_PRINTING), 110 Boolean.toString(myDebugPrettyPrintJsonLog)); 111 theProperties.put( 112 BackendSettings.backendKey(ElasticsearchBackendSettings.SCROLL_TIMEOUT), 113 Long.toString(myScrollTimeoutSecs)); 114 115 // This tells elasticsearch to use our custom index naming strategy. 116 theProperties.put( 117 BackendSettings.backendKey(ElasticsearchBackendSettings.LAYOUT_STRATEGY), 118 IndexNamePrefixLayoutStrategy.class.getName()); 119 120 // This tells hibernate search to use this custom file for creating index settings. We use this to add a custom 121 // max_ngram_diff 122 theProperties.put( 123 BackendSettings.backendKey(ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_SETTINGS_FILE), 124 "ca/uhn/fhir/jpa/elastic/index-settings.json"); 125 126 if (!StringUtils.isBlank(myAwsRegion)) { 127 theProperties.put(BackendSettings.backendKey(ElasticsearchAwsBackendSettings.REGION), myAwsRegion); 128 theProperties.put(BackendSettings.backendKey(ElasticsearchAwsBackendSettings.SIGNING_ENABLED), true); 129 if (!StringUtils.isBlank(myUsername) && !StringUtils.isBlank(myPassword)) { 130 theProperties.put( 131 BackendSettings.backendKey(ElasticsearchAwsBackendSettings.CREDENTIALS_TYPE), 132 ElasticsearchAwsCredentialsTypeNames.STATIC); 133 theProperties.put( 134 BackendSettings.backendKey(ElasticsearchAwsBackendSettings.CREDENTIALS_ACCESS_KEY_ID), 135 myUsername); 136 theProperties.put( 137 BackendSettings.backendKey(ElasticsearchAwsBackendSettings.CREDENTIALS_SECRET_ACCESS_KEY), 138 myPassword); 139 } else { 140 // Default to Standard IAM Auth provider if username or password is absent. 141 theProperties.put( 142 BackendSettings.backendKey(ElasticsearchAwsBackendSettings.CREDENTIALS_TYPE), 143 ElasticsearchAwsCredentialsTypeNames.DEFAULT); 144 } 145 } 146 } 147 148 public ElasticsearchHibernatePropertiesBuilder setRequiredIndexStatus(IndexStatus theRequiredIndexStatus) { 149 myRequiredIndexStatus = theRequiredIndexStatus; 150 return this; 151 } 152 153 public ElasticsearchHibernatePropertiesBuilder setHosts(String hosts) { 154 if (hosts.contains("://")) { 155 throw new ConfigurationException( 156 Msg.code(2139) 157 + "Elasticsearch URLs cannot include a protocol, that is a separate property. Remove http:// or https:// from this URL."); 158 } 159 myHosts = hosts; 160 return this; 161 } 162 163 public ElasticsearchHibernatePropertiesBuilder setProtocol(String theProtocol) { 164 myProtocol = theProtocol; 165 return this; 166 } 167 168 public ElasticsearchHibernatePropertiesBuilder setIndexSchemaManagementStrategy( 169 SchemaManagementStrategyName theIndexSchemaManagementStrategy) { 170 myIndexSchemaManagementStrategy = theIndexSchemaManagementStrategy; 171 return this; 172 } 173 174 public ElasticsearchHibernatePropertiesBuilder setIndexManagementWaitTimeoutMillis( 175 long theIndexManagementWaitTimeoutMillis) { 176 myIndexManagementWaitTimeoutMillis = theIndexManagementWaitTimeoutMillis; 177 return this; 178 } 179 180 public ElasticsearchHibernatePropertiesBuilder setScrollTimeoutSecs(long theScrollTimeoutSecs) { 181 myScrollTimeoutSecs = theScrollTimeoutSecs; 182 return this; 183 } 184 185 public ElasticsearchHibernatePropertiesBuilder setDebugIndexSyncStrategy(String theSyncStrategy) { 186 myDebugSyncStrategy = theSyncStrategy; 187 return this; 188 } 189 190 public ElasticsearchHibernatePropertiesBuilder setDebugPrettyPrintJsonLog(boolean theDebugPrettyPrintJsonLog) { 191 myDebugPrettyPrintJsonLog = theDebugPrettyPrintJsonLog; 192 return this; 193 } 194 195 /** 196 * If this is set to `true`, the AWS region will be used to configure the AWS client. Additionally, this will trigger 197 * HibernateSearch to attempt to use IAM Authentication. If the username and password are set in addition to the region, 198 * then the username and password will be used as the AWS_ACCESS_KEY_ID and AWS_SECRET_KEY_ID for a static credentials file for IAM. 199 * 200 * @param theAwsRegion The String version of the region, e.g. `us-east-2`. 201 * @return This builder. 202 */ 203 public ElasticsearchHibernatePropertiesBuilder setAwsRegion(String theAwsRegion) { 204 myAwsRegion = theAwsRegion; 205 return this; 206 } 207}