
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}