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  		validateIncludes("include", theSource.getCompose().getInclude());
67  		validateIncludes("exclude", theSource.getCompose().getExclude());
68  
69  		HapiWorkerContext workerContext = new HapiWorkerContext(getContext(), myValidationSupport);
70  
71  		ValueSetExpansionOutcome outcome = workerContext.expand(theSource, null);
72  
73  		ValueSet retVal = outcome.getValueset();
74  		retVal.setStatus(PublicationStatus.ACTIVE);
75  
76  		return retVal;
77  
78  		// ValueSetExpansionComponent expansion = outcome.getValueset().getExpansion();
79  		//
80  		// ValueSet retVal = new ValueSet();
81  		// retVal.getMeta().setLastUpdated(new Date());
82  		// retVal.setExpansion(expansion);
83  		// return retVal;
84  	}
85  
86  	private void validateIncludes(String name, List<ConceptSetComponent> listToValidate) {
87  		for (ConceptSetComponent nextExclude : listToValidate) {
88  			if (isBlank(nextExclude.getSystem()) && !ElementUtil.isEmpty(nextExclude.getConcept(), nextExclude.getFilter())) {
89  				throw new InvalidRequestException("ValueSet contains " + name + " criteria with no system defined");
90  			}
91  		}
92  	}
93  
94  	@Override
95  	public ValueSet expandByIdentifier(String theUri, String theFilter) {
96  		if (isBlank(theUri)) {
97  			throw new InvalidRequestException("URI must not be blank or missing");
98  		}
99  
100 		ValueSet source = new ValueSet();
101 
102 		source.getCompose().addInclude().addValueSet(theUri);
103 
104 		if (isNotBlank(theFilter)) {
105 			ConceptSetComponent include = source.getCompose().addInclude();
106 			ConceptSetFilterComponent filter = include.addFilter();
107 			filter.setProperty("display");
108 			filter.setOp(FilterOperator.EQUAL);
109 			filter.setValue(theFilter);
110 		}
111 
112 		ValueSet retVal = doExpand(source);
113 		return retVal;
114 
115 		// if (defaultValueSet != null) {
116 		// source = getContext().newJsonParser().parseResource(ValueSet.class, getContext().newJsonParser().encodeResourceToString(defaultValueSet));
117 		// } else {
118 		// IBundleProvider ids = search(ValueSet.SP_URL, new UriParam(theUri));
119 		// if (ids.size() == 0) {
120 		// throw new InvalidRequestException("Unknown ValueSet URI: " + theUri);
121 		// }
122 		// source = (ValueSet) ids.getResources(0, 1).get(0);
123 		// }
124 		//
125 		// return expand(defaultValueSet, theFilter);
126 
127 	}
128 
129 	@Override
130 	public ValueSet expand(ValueSet source, String theFilter) {
131 		ValueSet toExpand = new ValueSet();
132 
133 		// for (UriType next : source.getCompose().getInclude()) {
134 		// ConceptSetComponent include = toExpand.getCompose().addInclude();
135 		// include.setSystem(next.getValue());
136 		// addFilterIfPresent(theFilter, include);
137 		// }
138 
139 		for (ConceptSetComponent next : source.getCompose().getInclude()) {
140 			toExpand.getCompose().addInclude(next);
141 			addFilterIfPresent(theFilter, next);
142 		}
143 
144 		if (toExpand.getCompose().isEmpty()) {
145 			throw new InvalidRequestException("ValueSet does not have any compose.include or compose.import values, can not expand");
146 		}
147 
148 		toExpand.getCompose().getExclude().addAll(source.getCompose().getExclude());
149 
150 		ValueSet retVal = doExpand(toExpand);
151 
152 		if (isNotBlank(theFilter)) {
153 			applyFilter(retVal.getExpansion().getTotalElement(), retVal.getExpansion().getContains(), theFilter);
154 		}
155 
156 		return retVal;
157 
158 	}
159 
160 	private void applyFilter(IntegerType theTotalElement, List<ValueSetExpansionContainsComponent> theContains, String theFilter) {
161 
162 		for (int idx = 0; idx < theContains.size(); idx++) {
163 			ValueSetExpansionContainsComponent next = theContains.get(idx);
164 			if (isBlank(next.getDisplay()) || !org.apache.commons.lang3.StringUtils.containsIgnoreCase(next.getDisplay(), theFilter)) {
165 				theContains.remove(idx);
166 				idx--;
167 				if (theTotalElement.getValue() != null) {
168 					theTotalElement.setValue(theTotalElement.getValue() - 1);
169 				}
170 			}
171 			applyFilter(theTotalElement, next.getContains(), theFilter);
172 		}
173 	}
174 
175 	private void addFilterIfPresent(String theFilter, ConceptSetComponent include) {
176 		if (ElementUtil.isEmpty(include.getConcept())) {
177 			if (isNotBlank(theFilter)) {
178 				include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue(theFilter);
179 			}
180 		}
181 	}
182 
183 	@Override
184 	public ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
185 			IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
186 			CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
187 
188 		List<IIdType> valueSetIds = Collections.emptyList();
189 
190 		boolean haveCodeableConcept = theCodeableConcept != null && theCodeableConcept.getCoding().size() > 0;
191 		boolean haveCoding = theCoding != null && theCoding.isEmpty() == false;
192 		boolean haveCode = theCode != null && theCode.isEmpty() == false;
193 
194 		if (!haveCodeableConcept && !haveCoding && !haveCode) {
195 			throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
196 		}
197 		if (!LogicUtil.multiXor(haveCodeableConcept, haveCoding, haveCode)) {
198 			throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)");
199 		}
200 
201 		boolean haveIdentifierParam = theValueSetIdentifier != null && theValueSetIdentifier.isEmpty() == false;
202 		ValueSet vs = null;
203 		if (theId != null) {
204 			vs = read(theId, theRequestDetails);
205 		} else if (haveIdentifierParam) {
206 			vs = myValidationSupport.fetchResource(getContext(), ValueSet.class, theValueSetIdentifier.getValue());
207 			if (vs == null) {
208 				throw new InvalidRequestException("Unknown ValueSet identifier: " + theValueSetIdentifier.getValue());
209 			}
210 		} else {
211 			if (theCode == null || theCode.isEmpty()) {
212 				throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.");
213 			}
214 			// String code = theCode.getValue();
215 			// String system = toStringOrNull(theSystem);
216 			LookupCodeResult result = myCodeSystemDao.lookupCode(theCode, theSystem, null, null);
217 			if (result.isFound()) {
218 				ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult retVal = new ValidateCodeResult(true, "Found code", result.getCodeDisplay());
219 				return retVal;
220 			}
221 		}
222 
223 		if (vs != null) {
224 			ValueSet expansion = doExpand(vs);
225 			List<ValueSetExpansionContainsComponent> contains = expansion.getExpansion().getContains();
226 			ValidateCodeResult result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept);
227 			if (result != null) {
228 				if (theDisplay != null && isNotBlank(theDisplay.getValue()) && isNotBlank(result.getDisplay())) {
229 					if (!theDisplay.getValue().equals(result.getDisplay())) {
230 						return new ValidateCodeResult(false, "Display for code does not match", result.getDisplay());
231 					}
232 				}
233 				return result;
234 			}
235 		}
236 
237 		return new ValidateCodeResult(false, "Code not found", null);
238 
239 	}
240 
241 	private String toStringOrNull(IPrimitiveType<String> thePrimitive) {
242 		return thePrimitive != null ? thePrimitive.getValue() : null;
243 	}
244 
245 	private ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInContains(List<ValueSetExpansionContainsComponent> contains, String theSystem, String theCode,
246 			Coding theCoding, CodeableConcept theCodeableConcept) {
247 		for (ValueSetExpansionContainsComponent nextCode : contains) {
248 			ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept);
249 			if (result != null) {
250 				return result;
251 			}
252 
253 			String system = nextCode.getSystem();
254 			String code = nextCode.getCode();
255 
256 			if (isNotBlank(theCode)) {
257 				if (theCode.equals(code) && (isBlank(theSystem) || theSystem.equals(system))) {
258 					return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
259 				}
260 			} else if (theCoding != null) {
261 				if (StringUtils.equals(system, theCoding.getSystem()) && StringUtils.equals(code, theCoding.getCode())) {
262 					return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
263 				}
264 			} else {
265 				for (Coding next : theCodeableConcept.getCoding()) {
266 					if (StringUtils.equals(system, next.getSystem()) && StringUtils.equals(code, next.getCode())) {
267 						return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
268 					}
269 				}
270 			}
271 
272 		}
273 
274 		return null;
275 	}
276 
277 	@Override
278 	public void purgeCaches() {
279 		// nothing
280 	}
281 
282 }