001package ca.uhn.fhir.jpa.search.elastic;
002
003/*-
004 * #%L
005 * HAPI FHIR JPA Server
006 * %%
007 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.context.ConfigurationException;
025import ca.uhn.fhir.jpa.api.config.DaoConfig;
026import org.apache.commons.lang3.StringUtils;
027import org.hibernate.search.backend.elasticsearch.index.layout.IndexLayoutStrategy;
028import org.hibernate.search.backend.elasticsearch.logging.impl.Log;
029import org.hibernate.search.util.common.logging.impl.LoggerFactory;
030import org.springframework.beans.factory.annotation.Autowired;
031import org.springframework.stereotype.Service;
032
033import java.lang.invoke.MethodHandles;
034import java.util.regex.Matcher;
035import java.util.regex.Pattern;
036
037/**
038 * This class instructs hibernate search on how to create index names for indexed entities.
039 * In our case, we use this class to add an optional prefix to all indices which are created, which can be controlled via
040 * {@link DaoConfig#setElasticSearchIndexPrefix(String)}.
041 */
042@Service
043public class IndexNamePrefixLayoutStrategy implements IndexLayoutStrategy {
044
045        @Autowired
046        private DaoConfig myDaoConfig;
047
048        static final Log log = LoggerFactory.make(Log.class, MethodHandles.lookup());
049        public static final String NAME = "prefix";
050        public static final Pattern UNIQUE_KEY_EXTRACTION_PATTERN = Pattern.compile("(.*)-\\d{6}");
051
052        public String createInitialElasticsearchIndexName(String hibernateSearchIndexName) {
053                return addPrefixIfNecessary(hibernateSearchIndexName + "-000001");
054        }
055
056        public String createWriteAlias(String hibernateSearchIndexName) {
057                return addPrefixIfNecessary(hibernateSearchIndexName +"-write");
058        }
059
060        public String createReadAlias(String hibernateSearchIndexName) {
061                return addPrefixIfNecessary(hibernateSearchIndexName + "-read");
062        }
063
064        private String addPrefixIfNecessary(String theCandidateName) {
065                validateDaoConfigIsPresent();
066                if (!StringUtils.isBlank(myDaoConfig.getElasticSearchIndexPrefix())) {
067                        return myDaoConfig.getElasticSearchIndexPrefix() + "-" + theCandidateName;
068                } else {
069                        return theCandidateName;
070                }
071        }
072
073        public String extractUniqueKeyFromHibernateSearchIndexName(String hibernateSearchIndexName) {
074                return hibernateSearchIndexName;
075        }
076
077        public String extractUniqueKeyFromElasticsearchIndexName(String elasticsearchIndexName) {
078                Matcher matcher = UNIQUE_KEY_EXTRACTION_PATTERN.matcher(elasticsearchIndexName);
079                if (!matcher.matches()) {
080                        throw log.invalidIndexPrimaryName(elasticsearchIndexName, UNIQUE_KEY_EXTRACTION_PATTERN);
081                } else {
082                        String candidateUniqueKey= matcher.group(1);
083                        return removePrefixIfNecessary(candidateUniqueKey);
084                }
085        }
086
087        private String removePrefixIfNecessary(String theCandidateUniqueKey) {
088                validateDaoConfigIsPresent();
089                if (!StringUtils.isBlank(myDaoConfig.getElasticSearchIndexPrefix())) {
090                        return theCandidateUniqueKey.replace(myDaoConfig.getElasticSearchIndexPrefix() + "-", "");
091                } else {
092                        return theCandidateUniqueKey;
093                }
094        }
095        private void validateDaoConfigIsPresent() {
096                if (myDaoConfig == null) {
097                        throw new ConfigurationException(Msg.code(1168) + "While attempting to boot HAPI FHIR, the Hibernate Search bootstrapper failed to find the DaoConfig. This probably means Hibernate Search has been recently upgraded, or somebody modified HapiFhirLocalContainerEntityManagerFactoryBean.");
098                }
099        }
100}