001/*-
002 * #%L
003 * HAPI FHIR Storage api
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;
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.HashMap;
028import java.util.List;
029import java.util.Map;
030import java.util.Objects;
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                myMap.put(new Entry(theSource), new Entry(theTarget));
084                myReverseMap.put(new Entry(theTarget), new Entry(theSource));
085        }
086
087        public boolean isEmpty() {
088                return myMap.isEmpty();
089        }
090
091        /**
092         * Updates all targets of the map with a new id value if the input id has
093         * the same ResourceType and IdPart as the target id.
094         */
095        public void updateTargets(IIdType theNewId) {
096                if (theNewId == null) {
097                        return;
098                }
099                String newUnqualifiedVersionLessId = theNewId.toUnqualifiedVersionless().getValue();
100                entrySet().stream()
101                                .map(Pair::getValue)
102                                .filter(targetId ->
103                                                Objects.equals(targetId.toUnqualifiedVersionless().getValue(), newUnqualifiedVersionLessId))
104                                .forEach(targetId -> targetId.setValue(theNewId.getValue()));
105        }
106
107        private static class Entry {
108
109                private final String myUnversionedId;
110                private final IIdType myId;
111
112                private Entry(String theId) {
113                        myId = null;
114                        myUnversionedId = theId;
115                }
116
117                private Entry(IIdType theId) {
118                        String unversionedId = toVersionlessValue(theId);
119                        myUnversionedId = unversionedId;
120                        myId = theId;
121                }
122
123                @Override
124                public boolean equals(Object theOther) {
125                        if (theOther instanceof Entry) {
126                                String otherUnversionedId = ((Entry) theOther).myUnversionedId;
127                                if (myUnversionedId.equals(otherUnversionedId)) {
128                                        return true;
129                                }
130                        }
131                        return false;
132                }
133
134                @Override
135                public int hashCode() {
136                        return myUnversionedId.hashCode();
137                }
138        }
139
140        static String toVersionlessValue(IIdType theId) {
141                boolean isPlaceholder = theId.getValue().startsWith("urn:");
142                String unversionedId;
143                if (isPlaceholder || (!theId.hasBaseUrl() && !theId.hasVersionIdPart()) || !theId.hasResourceType()) {
144                        unversionedId = theId.getValue();
145                } else {
146                        unversionedId = theId.toUnqualifiedVersionless().getValue();
147                }
148                return unversionedId;
149        }
150}