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.api.config.JpaStorageSettings;
025import org.apache.commons.lang3.StringUtils;
026import org.hibernate.search.backend.elasticsearch.index.layout.IndexLayoutStrategy;
027import org.hibernate.search.backend.elasticsearch.logging.impl.Log;
028import org.hibernate.search.util.common.logging.impl.LoggerFactory;
029import org.springframework.beans.factory.annotation.Autowired;
030import org.springframework.stereotype.Service;
031
032import java.lang.invoke.MethodHandles;
033import java.util.regex.Matcher;
034import java.util.regex.Pattern;
035
036/**
037 * This class instructs hibernate search on how to create index names for indexed entities.
038 * In our case, we use this class to add an optional prefix to all indices which are created, which can be controlled via
039 * {@link JpaStorageSettings#setHSearchIndexPrefix(String)}.
040 */
041@Service
042public class IndexNamePrefixLayoutStrategy implements IndexLayoutStrategy {
043
044        @Autowired
045        private JpaStorageSettings myStorageSettings;
046
047        static final Log log = LoggerFactory.make(Log.class, MethodHandles.lookup());
048        public static final String NAME = "prefix";
049        public static final Pattern UNIQUE_KEY_EXTRACTION_PATTERN = Pattern.compile("(.*)-\\d{6}");
050
051        @Override
052        public String createInitialElasticsearchIndexName(String hibernateSearchIndexName) {
053                return addPrefixIfNecessary(hibernateSearchIndexName + "-000001");
054        }
055
056        @Override
057        public String createWriteAlias(String hibernateSearchIndexName) {
058                return addPrefixIfNecessary(hibernateSearchIndexName + "-write");
059        }
060
061        @Override
062        public String createReadAlias(String hibernateSearchIndexName) {
063                return addPrefixIfNecessary(hibernateSearchIndexName + "-read");
064        }
065
066        private String addPrefixIfNecessary(String theCandidateName) {
067                validateStorageSettingsIsPresent();
068                if (!StringUtils.isBlank(myStorageSettings.getHSearchIndexPrefix())) {
069                        return myStorageSettings.getHSearchIndexPrefix() + "-" + theCandidateName;
070                } else {
071                        return theCandidateName;
072                }
073        }
074
075        @Override
076        public String extractUniqueKeyFromHibernateSearchIndexName(String hibernateSearchIndexName) {
077                return hibernateSearchIndexName;
078        }
079
080        @Override
081        public String extractUniqueKeyFromElasticsearchIndexName(String elasticsearchIndexName) {
082                Matcher matcher = UNIQUE_KEY_EXTRACTION_PATTERN.matcher(elasticsearchIndexName);
083                if (!matcher.matches()) {
084                        throw log.invalidIndexPrimaryName(elasticsearchIndexName, UNIQUE_KEY_EXTRACTION_PATTERN);
085                } else {
086                        String candidateUniqueKey = matcher.group(1);
087                        return removePrefixIfNecessary(candidateUniqueKey);
088                }
089        }
090
091        private String removePrefixIfNecessary(String theCandidateUniqueKey) {
092                validateStorageSettingsIsPresent();
093                if (!StringUtils.isBlank(myStorageSettings.getHSearchIndexPrefix())) {
094                        return theCandidateUniqueKey.replace(myStorageSettings.getHSearchIndexPrefix() + "-", "");
095                } else {
096                        return theCandidateUniqueKey;
097                }
098        }
099
100        private void validateStorageSettingsIsPresent() {
101                if (myStorageSettings == null) {
102                        throw new ConfigurationException(
103                                        Msg.code(1168)
104                                                        + "While attempting to boot HAPI FHIR, the Hibernate Search bootstrapper failed to find the StorageSettings. This probably means Hibernate Search has been recently upgraded, or somebody modified HapiFhirLocalContainerEntityManagerFactoryBean.");
105                }
106        }
107}