001/*-
002 * #%L
003 * HAPI FHIR Storage api
004 * %%
005 * Copyright (C) 2014 - 2025 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;
021
022import com.google.common.collect.Multimap;
023import com.google.common.collect.MultimapBuilder;
024import org.apache.commons.lang3.tuple.Pair;
025import org.hl7.fhir.instance.model.api.IIdType;
026
027import java.util.Collection;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.stream.Collectors;
032
033public class IdSubstitutionMap {
034
035        private final Map<Entry, Entry> myMap = new HashMap<>();
036        private final Multimap<Entry, Entry> myReverseMap =
037                        MultimapBuilder.hashKeys().arrayListValues().build();
038
039        public boolean containsSource(IIdType theId) {
040                if (theId.isLocal()) {
041                        return false;
042                }
043                return myMap.containsKey(new Entry(theId));
044        }
045
046        public boolean containsSource(String theId) {
047                return myMap.containsKey(new Entry(theId));
048        }
049
050        public boolean containsTarget(IIdType theId) {
051                return myReverseMap.containsKey(new Entry(theId));
052        }
053
054        public boolean containsTarget(String theId) {
055                return myReverseMap.containsKey(new Entry(theId));
056        }
057
058        public IIdType getForSource(IIdType theId) {
059                Entry target = myMap.get(new Entry(theId));
060                if (target != null) {
061                        assert target.myId != null;
062                        return target.myId;
063                }
064                return null;
065        }
066
067        public IIdType getForSource(String theId) {
068                Entry target = myMap.get(new Entry(theId));
069                if (target != null) {
070                        assert target.myId != null;
071                        return target.myId;
072                }
073                return null;
074        }
075
076        public List<Pair<IIdType, IIdType>> entrySet() {
077                return myMap.entrySet().stream()
078                                .map(t -> Pair.of(t.getKey().myId, t.getValue().myId))
079                                .collect(Collectors.toList());
080        }
081
082        public void put(IIdType theSource, IIdType theTarget) {
083                Entry sourceEntry = new Entry(theSource);
084                Entry targetEntry = new Entry(theTarget);
085                myMap.put(sourceEntry, targetEntry);
086                myReverseMap.put(targetEntry, sourceEntry);
087        }
088
089        public boolean isEmpty() {
090                return myMap.isEmpty();
091        }
092
093        /**
094         * Updates all targets of the map with a new id value if the input id has
095         * the same ResourceType and IdPart as the target id.
096         */
097        public void updateTargets(IIdType theNewId) {
098                if (theNewId == null || theNewId.getValue() == null) {
099                        return;
100                }
101
102                Entry newEntry = new Entry(theNewId);
103                Collection<Entry> targets = myReverseMap.removeAll(newEntry);
104                for (Entry nextTarget : targets) {
105                        myMap.put(nextTarget, newEntry);
106                }
107                myReverseMap.putAll(newEntry, targets);
108        }
109
110        private static class Entry {
111
112                private final String myUnversionedId;
113                private final IIdType myId;
114
115                private Entry(String theId) {
116                        myId = null;
117                        myUnversionedId = theId;
118                }
119
120                private Entry(IIdType theId) {
121                        String unversionedId = toVersionlessValue(theId);
122                        myUnversionedId = unversionedId;
123                        myId = theId;
124                }
125
126                @Override
127                public boolean equals(Object theOther) {
128                        if (theOther instanceof Entry) {
129                                String otherUnversionedId = ((Entry) theOther).myUnversionedId;
130                                if (myUnversionedId.equals(otherUnversionedId)) {
131                                        return true;
132                                }
133                        }
134                        return false;
135                }
136
137                @Override
138                public int hashCode() {
139                        return myUnversionedId.hashCode();
140                }
141
142                @Override
143                public String toString() {
144                        return "Entry[" + "myUnversionedId='" + myUnversionedId + "', myId=" + myId + ']';
145                }
146        }
147
148        static String toVersionlessValue(IIdType theId) {
149                boolean isPlaceholder = theId.getValue().startsWith("urn:");
150                String unversionedId;
151                if (isPlaceholder || (!theId.hasBaseUrl() && !theId.hasVersionIdPart()) || !theId.hasResourceType()) {
152                        unversionedId = theId.getValue();
153                } else {
154                        unversionedId = theId.toUnqualifiedVersionless().getValue();
155                }
156                return unversionedId;
157        }
158}