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.dao.mdm;
021
022import org.apache.commons.lang3.StringUtils;
023import org.slf4j.Logger;
024
025import java.util.Map;
026import java.util.concurrent.ConcurrentHashMap;
027
028import static org.slf4j.LoggerFactory.getLogger;
029
030/**
031 * The purpose of this class is to share context between steps of a given GroupBulkExport job.
032 *
033 * This cache allows you to port state between reader/processor/writer. In this case, we are maintaining
034 * a cache of Source Resource ID -> Golden Resource ID, so that we can annotate outgoing resources with their golden owner
035 * if applicable.
036 *
037 */
038public class MdmExpansionCacheSvc {
039        private static final Logger ourLog = getLogger(MdmExpansionCacheSvc.class);
040
041        private final ConcurrentHashMap<String, String> mySourceToGoldenIdCache = new ConcurrentHashMap<>();
042
043        /**
044         * Lookup a given resource's golden resource ID in the cache. Note that if you pass this function the resource ID of a
045         * golden resource, it will just return itself.
046         *
047         * @param theSourceId the resource ID of the source resource ,e.g. PAT123
048         * @return the resource ID of the associated golden resource.
049         */
050        public String getGoldenResourceId(String theSourceId) {
051                ourLog.debug(buildLogMessage("About to lookup cached resource ID " + theSourceId));
052                String goldenResourceId = mySourceToGoldenIdCache.get(theSourceId);
053
054                // A golden resources' golden resource ID is itself.
055                if (StringUtils.isBlank(goldenResourceId)) {
056                        if (mySourceToGoldenIdCache.containsValue(theSourceId)) {
057                                goldenResourceId = theSourceId;
058                        }
059                }
060                return goldenResourceId;
061        }
062
063        private String buildLogMessage(String theMessage) {
064                return buildLogMessage(theMessage, false);
065        }
066
067        /**
068         * Builds a log message, potentially enriched with the cache content.
069         *
070         * @param message The log message
071         * @param theAddCacheContentContent If true, will annotate the log message with the current cache contents.
072         * @return a built log message, which may include the cache content.
073         */
074        public String buildLogMessage(String message, boolean theAddCacheContentContent) {
075                StringBuilder builder = new StringBuilder();
076                builder.append(message);
077                if (ourLog.isDebugEnabled() || theAddCacheContentContent) {
078                        builder.append("\n").append("Current cache content is:").append("\n");
079                        mySourceToGoldenIdCache.entrySet().stream().forEach(entry -> builder.append(entry.getKey())
080                                        .append(" -> ")
081                                        .append(entry.getValue())
082                                        .append("\n"));
083                        return builder.toString();
084                }
085                return builder.toString();
086        }
087
088        /**
089         * Populate the cache
090         *
091         * @param theSourceResourceIdToGoldenResourceIdMap the source ID -> golden ID map to populate the cache with.
092         */
093        public void setCacheContents(Map<String, String> theSourceResourceIdToGoldenResourceIdMap) {
094                if (mySourceToGoldenIdCache.isEmpty()) {
095                        this.mySourceToGoldenIdCache.putAll(theSourceResourceIdToGoldenResourceIdMap);
096                }
097        }
098
099        /**
100         * Since this cache is used at @JobScope, we can skip a whole whack of expansions happening by simply checking
101         * if one of our child steps has populated the cache yet. .
102         */
103        public boolean hasBeenPopulated() {
104                return !mySourceToGoldenIdCache.isEmpty();
105        }
106}