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