001package ca.uhn.fhir.jpa.term;
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.context.FhirContext;
024import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
025import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
026import ca.uhn.fhir.model.api.annotation.Block;
027import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
028import org.apache.commons.lang3.StringUtils;
029import org.hl7.fhir.r4.model.ValueSet;
030
031import javax.annotation.Nonnull;
032import javax.annotation.Nullable;
033import java.util.ArrayList;
034import java.util.Arrays;
035import java.util.Collection;
036import java.util.Collections;
037import java.util.HashMap;
038import java.util.List;
039import java.util.Map;
040import java.util.stream.Collectors;
041
042import static org.apache.commons.lang3.StringUtils.isNotBlank;
043
044@Block()
045public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.ValueSetExpansionComponent implements IValueSetConceptAccumulator {
046        private final int myMaxCapacity;
047        private final FhirContext myContext;
048        private int mySkipCountRemaining;
049        private int myHardExpansionMaximumSize;
050        private List<String> myMessages;
051        private int myAddedConcepts;
052        private Integer myTotalConcepts;
053        private Map<Long, ValueSet.ValueSetExpansionContainsComponent> mySourcePidToConcept = new HashMap<>();
054        private Map<ValueSet.ValueSetExpansionContainsComponent, String> myConceptToSourceDirectParentPids = new HashMap<>();
055        private boolean myTrackingHierarchy;
056
057        /**
058         * Constructor
059         *
060         * @param theMaxCapacity The maximum number of results this accumulator will accept before throwing
061         *                       an {@link InternalErrorException}
062         * @param theTrackingHierarchy
063         */
064        ValueSetExpansionComponentWithConceptAccumulator(FhirContext theContext, int theMaxCapacity, boolean theTrackingHierarchy) {
065                myMaxCapacity = theMaxCapacity;
066                myContext = theContext;
067                myTrackingHierarchy = theTrackingHierarchy;
068        }
069
070        @Nonnull
071        @Override
072        public Integer getCapacityRemaining() {
073                return (myMaxCapacity - myAddedConcepts) + mySkipCountRemaining;
074        }
075
076        public List<String> getMessages() {
077                if (myMessages == null) {
078                        return Collections.emptyList();
079                }
080                return Collections.unmodifiableList(myMessages);
081        }
082
083        @Override
084        public boolean isTrackingHierarchy() {
085                return myTrackingHierarchy;
086        }
087
088        @Override
089        public void addMessage(String theMessage) {
090                if (myMessages == null) {
091                        myMessages = new ArrayList<>();
092                }
093                myMessages.add(theMessage);
094        }
095
096        @Override
097        public void includeConcept(String theSystem, String theCode, String theDisplay, Long theSourceConceptPid, String theSourceConceptDirectParentPids, String theCodeSystemVersion) {
098                if (mySkipCountRemaining > 0) {
099                        mySkipCountRemaining--;
100                        return;
101                }
102
103                incrementConceptsCount();
104
105                ValueSet.ValueSetExpansionContainsComponent contains = this.addContains();
106                setSystemAndVersion(theSystem, contains);
107                contains.setCode(theCode);
108                contains.setDisplay(theDisplay);
109                contains.setVersion(theCodeSystemVersion);
110        }
111
112        @Override
113        public void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, Collection<TermConceptDesignation> theDesignations, Long theSourceConceptPid, String theSourceConceptDirectParentPids, String theCodeSystemVersion) {
114                if (mySkipCountRemaining > 0) {
115                        mySkipCountRemaining--;
116                        return;
117                }
118
119                incrementConceptsCount();
120
121                ValueSet.ValueSetExpansionContainsComponent contains = this.addContains();
122
123                if (theSourceConceptPid != null) {
124                        mySourcePidToConcept.put(theSourceConceptPid, contains);
125                }
126                if (theSourceConceptDirectParentPids != null) {
127                        myConceptToSourceDirectParentPids.put(contains, theSourceConceptDirectParentPids);
128                }
129
130                setSystemAndVersion(theSystem, contains);
131                contains.setCode(theCode);
132                contains.setDisplay(theDisplay);
133
134                if (isNotBlank(theCodeSystemVersion)) {
135                        contains.setVersion(theCodeSystemVersion);
136                }
137
138                if (theDesignations != null) {
139                        for (TermConceptDesignation termConceptDesignation : theDesignations) {
140                                contains
141                                        .addDesignation()
142                                        .setValue(termConceptDesignation.getValue())
143                                        .setLanguage(termConceptDesignation.getLanguage())
144                                        .getUse()
145                                        .setSystem(termConceptDesignation.getUseSystem())
146                                        .setCode(termConceptDesignation.getUseCode())
147                                        .setDisplay(termConceptDesignation.getUseDisplay());
148                        }
149                }
150        }
151
152        @Override
153        public void consumeSkipCount(int theSkipCountToConsume) {
154                mySkipCountRemaining -= theSkipCountToConsume;
155        }
156
157        @Nullable
158        @Override
159        public Integer getSkipCountRemaining() {
160                return mySkipCountRemaining;
161        }
162
163        @Override
164        public boolean excludeConcept(String theSystem, String theCode) {
165                String excludeSystem;
166                String excludeSystemVersion;
167                int versionSeparator = theSystem.indexOf("|");
168                if (versionSeparator > -1) {
169                        excludeSystemVersion = theSystem.substring(versionSeparator + 1);
170                        excludeSystem = theSystem.substring(0, versionSeparator);
171                } else {
172                        excludeSystem = theSystem;
173                        excludeSystemVersion = null;
174                }
175                if (excludeSystemVersion != null) {
176                        return this.getContains().removeIf(t ->
177                                excludeSystem.equals(t.getSystem()) &&
178                                        theCode.equals(t.getCode()) &&
179                                        excludeSystemVersion.equals(t.getVersion()));
180                } else {
181                        return this.getContains().removeIf(t ->
182                                theSystem.equals(t.getSystem()) &&
183                                        theCode.equals(t.getCode()));
184                }
185        }
186
187        private void incrementConceptsCount() {
188                Integer capacityRemaining = getCapacityRemaining();
189                if (capacityRemaining == 0) {
190                        String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionTooLarge", myMaxCapacity);
191                        throw new ExpansionTooCostlyException(msg);
192                }
193
194                if (myHardExpansionMaximumSize > 0 && myAddedConcepts > myHardExpansionMaximumSize) {
195                        String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionTooLarge", myHardExpansionMaximumSize);
196                        throw new ExpansionTooCostlyException(msg);
197                }
198
199                myAddedConcepts++;
200        }
201
202        public Integer getTotalConcepts() {
203                return myTotalConcepts;
204        }
205
206        @Override
207        public void incrementOrDecrementTotalConcepts(boolean theAdd, int theDelta) {
208                int delta = theDelta;
209                if (!theAdd) {
210                        delta = -delta;
211                }
212                if (myTotalConcepts == null) {
213                        myTotalConcepts = delta;
214                } else {
215                        myTotalConcepts = myTotalConcepts + delta;
216                }
217        }
218
219        private void setSystemAndVersion(String theSystemAndVersion, ValueSet.ValueSetExpansionContainsComponent myComponent) {
220                if (StringUtils.isNotEmpty((theSystemAndVersion))) {
221                        int versionSeparator = theSystemAndVersion.lastIndexOf('|');
222                        if (versionSeparator != -1) {
223                                myComponent.setVersion(theSystemAndVersion.substring(versionSeparator + 1));
224                                myComponent.setSystem(theSystemAndVersion.substring(0, versionSeparator));
225                        } else {
226                                myComponent.setSystem(theSystemAndVersion);
227                        }
228                }
229        }
230
231        public void setSkipCountRemaining(int theSkipCountRemaining) {
232                mySkipCountRemaining = theSkipCountRemaining;
233        }
234
235        public void setHardExpansionMaximumSize(int theHardExpansionMaximumSize) {
236                myHardExpansionMaximumSize = theHardExpansionMaximumSize;
237        }
238
239        public void applyHierarchy() {
240                for (int i = 0; i < this.getContains().size(); i++) {
241                        ValueSet.ValueSetExpansionContainsComponent nextContains = this.getContains().get(i);
242
243                        String directParentPidsString = myConceptToSourceDirectParentPids.get(nextContains);
244                        if (isNotBlank(directParentPidsString)) {
245                                List<Long> directParentPids = Arrays.stream(directParentPidsString.split(" ")).map(t -> Long.parseLong(t)).collect(Collectors.toList());
246
247                                boolean firstMatch = false;
248                                for (Long next : directParentPids) {
249                                        ValueSet.ValueSetExpansionContainsComponent parentConcept = mySourcePidToConcept.get(next);
250                                        if (parentConcept != null) {
251                                                if (!firstMatch) {
252                                                        firstMatch = true;
253                                                        this.getContains().remove(i);
254                                                        i--;
255                                                }
256
257                                                parentConcept.addContains(nextContains);
258                                        }
259                                }
260                        }
261                }
262        }
263}