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 ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem;
24  import ca.uhn.fhir.jpa.dao.SearchParameterMap;
25  import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
26  import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
27  import ca.uhn.fhir.jpa.entity.ResourceTable;
28  import ca.uhn.fhir.jpa.entity.TermCodeSystem;
29  import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
30  import ca.uhn.fhir.jpa.entity.TermConcept;
31  import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
32  import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
33  import ca.uhn.fhir.jpa.util.LogicUtil;
34  import ca.uhn.fhir.rest.api.server.RequestDetails;
35  import ca.uhn.fhir.rest.param.TokenParam;
36  import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
37  import org.hl7.fhir.instance.model.api.IBaseResource;
38  import org.hl7.fhir.instance.model.api.IIdType;
39  import org.hl7.fhir.instance.model.api.IPrimitiveType;
40  import org.hl7.fhir.r4.hapi.ctx.IValidationSupport.CodeValidationResult;
41  import org.hl7.fhir.r4.hapi.ctx.ValidationSupportChain;
42  import org.hl7.fhir.r4.model.CodeSystem;
43  import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode;
44  import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
45  import org.hl7.fhir.r4.model.CodeableConcept;
46  import org.hl7.fhir.r4.model.Coding;
47  import org.hl7.fhir.r4.model.IdType;
48  import org.springframework.beans.factory.annotation.Autowired;
49  
50  import javax.servlet.http.HttpServletRequest;
51  import java.util.ArrayList;
52  import java.util.Date;
53  import java.util.List;
54  import java.util.Set;
55  
56  import static org.apache.commons.lang3.StringUtils.isBlank;
57  import static org.apache.commons.lang3.StringUtils.isNotBlank;
58  
59  public class FhirResourceDaoCodeSystemR4 extends FhirResourceDaoR4<CodeSystem> implements IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> {
60  
61  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoCodeSystemR4.class);
62  
63  	@Autowired
64  	private ITermCodeSystemVersionDao myCsvDao;
65  	@Autowired
66  	private ITermCodeSystemDao myCsDao;
67  	@Autowired
68  	private IHapiTerminologySvc myTerminologySvc;
69  	@Autowired
70  	private ValidationSupportChain myValidationSupport;
71  
72  	@Override
73  	public List<IIdType> findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem) {
74  		List<IIdType> valueSetIds;
75  		Set<Long> ids = searchForIds(new SearchParameterMap(CodeSystem.SP_CODE, new TokenParam(theSystem, theCode)));
76  		valueSetIds = new ArrayList<>();
77  		for (Long next : ids) {
78  			valueSetIds.add(new IdType("CodeSystem", next));
79  		}
80  		return valueSetIds;
81  	}
82  
83  	@Override
84  	public LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, RequestDetails theRequestDetails) {
85  		boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode());
86  		boolean haveCode = theCode != null && theCode.isEmpty() == false;
87  		boolean haveSystem = theSystem != null && theSystem.isEmpty() == false;
88  
89  		if (!haveCoding && !(haveSystem && haveCode)) {
90  			throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
91  		}
92  		if (!LogicUtil.multiXor(haveCoding, (haveSystem && haveCode)) || (haveSystem != haveCode)) {
93  			throw new InvalidRequestException("$lookup can only validate (system AND code) OR (coding.system AND coding.code)");
94  		}
95  
96  		String code;
97  		String system;
98  		if (haveCoding) {
99  			code = theCoding.getCode();
100 			system = theCoding.getSystem();
101 		} else {
102 			code = theCode.getValue();
103 			system = theSystem.getValue();
104 		}
105 
106 		ourLog.info("Looking up {} / {}", system, code);
107 
108 		if (myValidationSupport.isCodeSystemSupported(getContext(), system)) {
109 
110 			ourLog.info("Code system {} is supported", system);
111 
112 			CodeValidationResult result = myValidationSupport.validateCode(getContext(), system, code, null);
113 			if (result != null) {
114 				if (result.isOk()) {
115 					LookupCodeResult retVal = new LookupCodeResult();
116 					retVal.setFound(true);
117 					retVal.setSearchedForCode(code);
118 					retVal.setSearchedForSystem(system);
119 					retVal.setCodeDisplay(result.asConceptDefinition().getDisplay());
120 
121 					String codeSystemDisplayName = result.getCodeSystemName();
122 					if (isBlank(codeSystemDisplayName)) {
123 						codeSystemDisplayName = "Unknown";
124 					}
125 
126 					retVal.setCodeSystemDisplayName(codeSystemDisplayName);
127 					retVal.setCodeSystemVersion(result.getCodeSystemVersion());
128 					retVal.setProperties(result.getProperties());
129 
130 					return retVal;
131 				}
132 			}
133 
134 		}
135 
136 		// We didn't find it..
137 		LookupCodeResult retVal = new LookupCodeResult();
138 		retVal.setFound(false);
139 		retVal.setSearchedForCode(code);
140 		retVal.setSearchedForSystem(system);
141 		return retVal;
142 
143 	}
144 
145 	@Override
146 	protected void preDelete(CodeSystem theResourceToDelete, ResourceTable theEntityToDelete) {
147 		super.preDelete(theResourceToDelete, theEntityToDelete);
148 
149 		String codeSystemUrl = theResourceToDelete.getUrl();
150 		if (isNotBlank(codeSystemUrl)) {
151 			TermCodeSystem persCs = myCsDao.findByCodeSystemUri(codeSystemUrl);
152 			if (persCs != null) {
153 				myTerminologySvc.deleteCodeSystem(persCs);
154 			}
155 		}
156 	}
157 
158 	private List<TermConcept> toPersistedConcepts(List<ConceptDefinitionComponent> theConcept, TermCodeSystemVersion theCodeSystemVersion) {
159 		ArrayList<TermConcept> retVal = new ArrayList<>();
160 
161 		for (ConceptDefinitionComponent next : theConcept) {
162 			if (isNotBlank(next.getCode())) {
163 				TermConcept termConcept = new TermConcept();
164 				termConcept.setCode(next.getCode());
165 				termConcept.setCodeSystemVersion(theCodeSystemVersion);
166 				termConcept.setDisplay(next.getDisplay());
167 				termConcept.addChildren(toPersistedConcepts(next.getConcept(), theCodeSystemVersion), RelationshipTypeEnum.ISA);
168 				retVal.add(termConcept);
169 			}
170 		}
171 
172 		return retVal;
173 	}
174 
175 	@Override
176 	protected ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
177 													 boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
178 		ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
179 
180 		CodeSystem cs = (CodeSystem) theResource;
181 
182 		if (cs != null && isNotBlank(cs.getUrl())) {
183 			String codeSystemUrl = cs.getUrl();
184 			if (cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == null) {
185 				ourLog.info("CodeSystem {} has a status of {}, going to store concepts in terminology tables", retVal.getIdDt().getValue(), cs.getContentElement().getValueAsString());
186 
187 				Long codeSystemResourcePid = retVal.getId();
188 				TermCodeSystemVersion persCs = myCsvDao.findCurrentVersionForCodeSystemResourcePid(codeSystemResourcePid);
189 				if (persCs != null) {
190 					ourLog.info("Code system version already exists in database");
191 				} else {
192 
193 					persCs = new TermCodeSystemVersion();
194 					persCs.setResource(retVal);
195 					persCs.getConcepts().addAll(toPersistedConcepts(cs.getConcept(), persCs));
196 					ourLog.info("Code system has {} concepts", persCs.getConcepts().size());
197 					myTerminologySvc.storeNewCodeSystemVersion(codeSystemResourcePid, codeSystemUrl, cs.getName(), persCs);
198 
199 				}
200 
201 			}
202 		}
203 
204 		return retVal;
205 	}
206 
207 }