View Javadoc
1   package ca.uhn.fhir.jpa.dao.r4;
2   
3   /*
4    * #%L
5    * HAPI FHIR JPA Server
6    * %%
7    * Copyright (C) 2014 - 2018 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   * http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import static org.apache.commons.lang3.StringUtils.isBlank;
24  import static org.apache.commons.lang3.StringUtils.isNotBlank;
25  
26  import java.util.Collections;
27  import java.util.List;
28  
29  import org.apache.commons.codec.binary.StringUtils;
30  import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
31  import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
32  import org.hl7.fhir.r4.model.*;
33  import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
34  import org.hl7.fhir.r4.model.ValueSet.*;
35  import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
36  import org.hl7.fhir.instance.model.api.IIdType;
37  import org.hl7.fhir.instance.model.api.IPrimitiveType;
38  import org.springframework.beans.factory.annotation.Autowired;
39  import org.springframework.beans.factory.annotation.Qualifier;
40  
41  import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem;
42  import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult;
43  import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
44  import ca.uhn.fhir.jpa.util.LogicUtil;
45  import ca.uhn.fhir.rest.api.server.RequestDetails;
46  import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
47  import ca.uhn.fhir.util.ElementUtil;
48  
49  public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4<ValueSet> implements IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> {
50  
51  	@Autowired
52  	@Qualifier("myJpaValidationSupportChainR4")
53  	private IValidationSupport myValidationSupport;
54  
55  	@Autowired
56  	private IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> myCodeSystemDao;
57  
58  	@Override
59  	public ValueSet expand(IIdType theId, String theFilter, RequestDetails theRequestDetails) {
60  		ValueSet source = read(theId, theRequestDetails);
61  		return expand(source, theFilter);
62  	}
63  
64  	private ValueSet doExpand(ValueSet theSource) {
65  
66  		/*
67  		 * If all of the code systems are supported by the HAPI FHIR terminology service, let's
68  		 * use that as it's more efficient.
69  		 */
70  
71  		boolean allSystemsAreSuppportedByTerminologyService = true;
72  		for (ConceptSetComponent next : theSource.getCompose().getInclude()) {
73  			if (!isBlank(next.getSystem()) && !myTerminologySvc.supportsSystem(next.getSystem())) {
74  				allSystemsAreSuppportedByTerminologyService = false;
75  			}
76  		}
77  		for (ConceptSetComponent next : theSource.getCompose().getExclude()) {
78  			if (!isBlank(next.getSystem()) && !myTerminologySvc.supportsSystem(next.getSystem())) {
79  				allSystemsAreSuppportedByTerminologyService = false;
80  			}
81  		}
82  		if (allSystemsAreSuppportedByTerminologyService) {
83  			return myTerminologySvc.expandValueSet(theSource);
84  		}
85  
86  		HapiWorkerContext workerContext = new HapiWorkerContext(getContext(), myValidationSupport);
87  
88  		ValueSetExpansionOutcome outcome = workerContext.expand(theSource, null);
89  
90  		ValueSet retVal = outcome.getValueset();
91  		retVal.setStatus(PublicationStatus.ACTIVE);
92  
93  		return retVal;
94  
95  		// ValueSetExpansionComponent expansion = outcome.getValueset().getExpansion();
96  		//
97  		// ValueSet retVal = new ValueSet();
98  		// retVal.getMeta().setLastUpdated(new Date());
99  		// retVal.setExpansion(expansion);
100 		// return retVal;
101 	}
102 
103 	private void validateIncludes(String name, List<ConceptSetComponent> listToValidate) {
104 		for (ConceptSetComponent nextExclude : listToValidate) {
105 			if (isBlank(nextExclude.getSystem()) && !ElementUtil.isEmpty(nextExclude.getConcept(), nextExclude.getFilter())) {
106 				throw new InvalidRequestException("ValueSet contains " + name + " criteria with no system defined");
107 			}
108 		}
109 	}
110 
111 	@Override
112 	public ValueSet expandByIdentifier(String theUri, String theFilter) {
113 		if (isBlank(theUri)) {
114 			throw new InvalidRequestException("URI must not be blank or missing");
115 		}
116 
117 		ValueSet source = new ValueSet();
118 
119 		source.getCompose().addInclude().addValueSet(theUri);
120 
121 		if (isNotBlank(theFilter)) {
122 			ConceptSetComponent include = source.getCompose().addInclude();
123 			ConceptSetFilterComponent filter = include.addFilter();
124 			filter.setProperty("display");
125 			filter.setOp(FilterOperator.EQUAL);
126 			filter.setValue(theFilter);
127 		}
128 
129 		ValueSet retVal = doExpand(source);
130 		return retVal;
131 
132 		// if (defaultValueSet != null) {
133 		// source = getContext().newJsonParser().parseResource(ValueSet.class, getContext().newJsonParser().encodeResourceToString(defaultValueSet));
134 		// } else {
135 		// IBundleProvider ids = search(ValueSet.SP_URL, new UriParam(theUri));
136 		// if (ids.size() == 0) {
137 		// throw new InvalidRequestException("Unknown ValueSet URI: " + theUri);
138 		// }
139 		// source = (ValueSet) ids.getResources(0, 1).get(0);
140 		// }
141 		//
142 		// return expand(defaultValueSet, theFilter);
143 
144 	}
145 
146 	@Override
147 	public ValueSet expand(ValueSet source, String theFilter) {
148 		ValueSet toExpand = new ValueSet();
149 
150 		// for (UriType next : source.getCompose().getInclude()) {
151 		// ConceptSetComponent include = toExpand.getCompose().addInclude();
152 		// include.setSystem(next.getValue());
153 		// addFilterIfPresent(theFilter, include);
154 		// }
155 
156 		for (ConceptSetComponent next : source.getCompose().getInclude()) {
157 			toExpand.getCompose().addInclude(next);
158 			addFilterIfPresent(theFilter, next);
159 		}
160 
161 		if (toExpand.getCompose().isEmpty()) {
162 			throw new InvalidRequestException("ValueSet does not have any compose.include or compose.import values, can not expand");
163 		}
164 
165 		toExpand.getCompose().getExclude().addAll(source.getCompose().getExclude());
166 
167 		ValueSet retVal = doExpand(toExpand);
168 
169 		if (isNotBlank(theFilter)) {
170 			applyFilter(retVal.getExpansion().getTotalElement(), retVal.getExpansion().getContains(), theFilter);
171 		}
172 
173 		return retVal;
174 
175 	}
176 
177 	private void applyFilter(IntegerType theTotalElement, List<ValueSetExpansionContainsComponent> theContains, String theFilter) {
178 
179 		for (int idx = 0; idx < theContains.size(); idx++) {
180 			ValueSetExpansionContainsComponent next = theContains.get(idx);
181 			if (isBlank(next.getDisplay()) || !org.apache.commons.lang3.StringUtils.containsIgnoreCase(next.getDisplay(), theFilter)) {
182 				theContains.remove(idx);
183 				idx--;
184 				if (theTotalElement.getValue() != null) {
185 					theTotalElement.setValue(theTotalElement.getValue() - 1);
186 				}
187 			}
188 			applyFilter(theTotalElement, next.getContains(), theFilter);
189 		}
190 	}
191 
192 	private void addFilterIfPresent(String theFilter, ConceptSetComponent include) {
193 		if (ElementUtil.isEmpty(include.getConcept())) {
194 			if (isNotBlank(theFilter)) {
195 				include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue(theFilter);
196 			}
197 		}
198 	}
199 
200 	@Override
201 	public ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
202 			IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
203 			CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
204 
205 		List<IIdType> valueSetIds = Collections.emptyList();
206 
207 		boolean haveCodeableConcept = theCodeableConcept != null && theCodeableConcept.getCoding().size() > 0;
208 		boolean haveCoding = theCoding != null && theCoding.isEmpty() == false;
209 		boolean haveCode = theCode != null && theCode.isEmpty() == false;
210 
211 		if (!haveCodeableConcept && !haveCoding && !haveCode) {
212 			throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
213 		}
214 		if (!LogicUtil.multiXor(haveCodeableConcept, haveCoding, haveCode)) {
215 			throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)");
216 		}
217 
218 		boolean haveIdentifierParam = theValueSetIdentifier != null && theValueSetIdentifier.isEmpty() == false;
219 		ValueSet vs = null;
220 		if (theId != null) {
221 			vs = read(theId, theRequestDetails);
222 		} else if (haveIdentifierParam) {
223 			vs = myValidationSupport.fetchResource(getContext(), ValueSet.class, theValueSetIdentifier.getValue());
224 			if (vs == null) {
225 				throw new InvalidRequestException("Unknown ValueSet identifier: " + theValueSetIdentifier.getValue());
226 			}
227 		} else {
228 			if (theCode == null || theCode.isEmpty()) {
229 				throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.");
230 			}
231 			// String code = theCode.getValue();
232 			// String system = toStringOrNull(theSystem);
233 			LookupCodeResult result = myCodeSystemDao.lookupCode(theCode, theSystem, null, null);
234 			if (result.isFound()) {
235 				ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult retVal = new ValidateCodeResult(true, "Found code", result.getCodeDisplay());
236 				return retVal;
237 			}
238 		}
239 
240 		if (vs != null) {
241 			ValueSet expansion = doExpand(vs);
242 			List<ValueSetExpansionContainsComponent> contains = expansion.getExpansion().getContains();
243 			ValidateCodeResult result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept);
244 			if (result != null) {
245 				if (theDisplay != null && isNotBlank(theDisplay.getValue()) && isNotBlank(result.getDisplay())) {
246 					if (!theDisplay.getValue().equals(result.getDisplay())) {
247 						return new ValidateCodeResult(false, "Display for code does not match", result.getDisplay());
248 					}
249 				}
250 				return result;
251 			}
252 		}
253 
254 		return new ValidateCodeResult(false, "Code not found", null);
255 
256 	}
257 
258 	private String toStringOrNull(IPrimitiveType<String> thePrimitive) {
259 		return thePrimitive != null ? thePrimitive.getValue() : null;
260 	}
261 
262 	private ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInContains(List<ValueSetExpansionContainsComponent> contains, String theSystem, String theCode,
263 			Coding theCoding, CodeableConcept theCodeableConcept) {
264 		for (ValueSetExpansionContainsComponent nextCode : contains) {
265 			ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept);
266 			if (result != null) {
267 				return result;
268 			}
269 
270 			String system = nextCode.getSystem();
271 			String code = nextCode.getCode();
272 
273 			if (isNotBlank(theCode)) {
274 				if (theCode.equals(code) && (isBlank(theSystem) || theSystem.equals(system))) {
275 					return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
276 				}
277 			} else if (theCoding != null) {
278 				if (StringUtils.equals(system, theCoding.getSystem()) && StringUtils.equals(code, theCoding.getCode())) {
279 					return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
280 				}
281 			} else {
282 				for (Coding next : theCodeableConcept.getCoding()) {
283 					if (StringUtils.equals(system, next.getSystem()) && StringUtils.equals(code, next.getCode())) {
284 						return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
285 					}
286 				}
287 			}
288 
289 		}
290 
291 		return null;
292 	}
293 
294 	@Override
295 	public void purgeCaches() {
296 		// nothing
297 	}
298 
299 }