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