View Javadoc
1   package ca.uhn.fhir.jpa.entity;
2   
3   import ca.uhn.fhir.context.support.IContextValidationSupport;
4   import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
5   import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor;
6   import ca.uhn.fhir.util.ValidateUtil;
7   import org.apache.commons.lang3.Validate;
8   import org.apache.commons.lang3.builder.EqualsBuilder;
9   import org.apache.commons.lang3.builder.HashCodeBuilder;
10  import org.apache.commons.lang3.builder.ToStringBuilder;
11  import org.apache.commons.lang3.builder.ToStringStyle;
12  import org.hibernate.search.annotations.*;
13  import org.hl7.fhir.r4.model.Coding;
14  
15  import javax.annotation.Nonnull;
16  import javax.persistence.*;
17  import javax.persistence.Index;
18  import java.io.Serializable;
19  import java.util.*;
20  
21  import static org.apache.commons.lang3.StringUtils.isNotBlank;
22  
23  /*
24   * #%L
25   * HAPI FHIR JPA Server
26   * %%
27   * Copyright (C) 2014 - 2018 University Health Network
28   * %%
29   * Licensed under the Apache License, Version 2.0 (the "License");
30   * you may not use this file except in compliance with the License.
31   * You may obtain a copy of the License at
32   * 
33   *      http://www.apache.org/licenses/LICENSE-2.0
34   * 
35   * Unless required by applicable law or agreed to in writing, software
36   * distributed under the License is distributed on an "AS IS" BASIS,
37   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
38   * See the License for the specific language governing permissions and
39   * limitations under the License.
40   * #L%
41   */
42  
43  @Entity
44  @Indexed(interceptor = DeferConceptIndexingInterceptor.class)
45  @Table(name = "TRM_CONCEPT", uniqueConstraints = {
46  	@UniqueConstraint(name = "IDX_CONCEPT_CS_CODE", columnNames = {"CODESYSTEM_PID", "CODE"})
47  }, indexes = {
48  	@Index(name = "IDX_CONCEPT_INDEXSTATUS", columnList = "INDEX_STATUS")
49  })
50  public class TermConcept implements Serializable {
51  	protected static final int MAX_DESC_LENGTH = 400;
52  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TermConcept.class);
53  
54  	private static final long serialVersionUID = 1L;
55  
56  	@OneToMany(fetch = FetchType.LAZY, mappedBy = "myParent", cascade = {})
57  	private Collection<TermConceptParentChildLink> myChildren;
58  
59  	@Column(name = "CODE", length = 100, nullable = false)
60  	@Fields({@Field(name = "myCode", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "exactAnalyzer")),})
61  	private String myCode;
62  
63  	@ManyToOne()
64  	@JoinColumn(name = "CODESYSTEM_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPT_PID_CS_PID"))
65  	private TermCodeSystemVersion myCodeSystem;
66  
67  	@Column(name = "CODESYSTEM_PID", insertable = false, updatable = false)
68  	@Fields({@Field(name = "myCodeSystemVersionPid")})
69  	private long myCodeSystemVersionPid;
70  
71  	@Column(name = "DISPLAY", length = MAX_DESC_LENGTH, nullable = true)
72  	@Fields({
73  		@Field(name = "myDisplay", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")),
74  		@Field(name = "myDisplayEdgeNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteEdgeAnalyzer")),
75  		@Field(name = "myDisplayNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteNGramAnalyzer")),
76  		@Field(name = "myDisplayPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer"))
77  	})
78  	private String myDisplay;
79  
80  	@OneToMany(mappedBy = "myConcept", orphanRemoval = true)
81  	@Field
82  	@FieldBridge(impl = TermConceptPropertyFieldBridge.class)
83  	private Collection<TermConceptProperty> myProperties;
84  
85  	@OneToMany(mappedBy = "myConcept", orphanRemoval = true)
86  	private Collection<TermConceptDesignation> myDesignations;
87  
88  	@Id()
89  	@SequenceGenerator(name = "SEQ_CONCEPT_PID", sequenceName = "SEQ_CONCEPT_PID")
90  	@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PID")
91  	@Column(name = "PID")
92  	private Long myId;
93  	@Column(name = "INDEX_STATUS", nullable = true)
94  	private Long myIndexStatus;
95  	@Transient
96  	@Field(name = "myParentPids", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "conceptParentPidsAnalyzer"))
97  	private String myParentPids;
98  	@OneToMany(cascade = {}, fetch = FetchType.LAZY, mappedBy = "myChild")
99  	private Collection<TermConceptParentChildLink> myParents;
100 	@Column(name = "CODE_SEQUENCE", nullable = true)
101 	private Integer mySequence;
102 
103 	public TermConcept() {
104 		super();
105 	}
106 
107 	public TermConcept(TermCodeSystemVersion theCs, String theCode) {
108 		setCodeSystemVersion(theCs);
109 		setCode(theCode);
110 	}
111 
112 	public TermConcept addChild(TermConcept theChild, RelationshipTypeEnum theRelationshipType) {
113 		Validate.notNull(theRelationshipType, "theRelationshipType must not be null");
114 		TermConceptParentChildLink link = new TermConceptParentChildLink();
115 		link.setParent(this);
116 		link.setChild(theChild);
117 		link.setRelationshipType(theRelationshipType);
118 		getChildren().add(link);
119 
120 		theChild.getParents().add(link);
121 		return this;
122 	}
123 
124 	public void addChildren(List<TermConcept> theChildren, RelationshipTypeEnum theRelationshipType) {
125 		for (TermConcept next : theChildren) {
126 			addChild(next, theRelationshipType);
127 		}
128 	}
129 
130 	public TermConceptDesignation addDesignation() {
131 		TermConceptDesignation designation = new TermConceptDesignation();
132 		designation.setConcept(this);
133 		getDesignations().add(designation);
134 		return designation;
135 	}
136 
137 	private TermConceptProperty addProperty(@Nonnull TermConceptPropertyTypeEnum thePropertyType, @Nonnull String thePropertyName, @Nonnull String thePropertyValue) {
138 		Validate.notBlank(thePropertyName);
139 
140 		TermConceptProperty property = new TermConceptProperty();
141 		property.setConcept(this);
142 		property.setType(thePropertyType);
143 		property.setKey(thePropertyName);
144 		property.setValue(thePropertyValue);
145 		getProperties().add(property);
146 
147 		return property;
148 	}
149 
150 	public TermConceptProperty addPropertyCoding(@Nonnull String thePropertyName, @Nonnull String thePropertyCodeSystem, @Nonnull String thePropertyCode, String theDisplayName) {
151 		return addProperty(TermConceptPropertyTypeEnum.CODING, thePropertyName, thePropertyCode)
152 			.setCodeSystem(thePropertyCodeSystem)
153 			.setDisplay(theDisplayName);
154 	}
155 
156 	public TermConceptProperty addPropertyString(@Nonnull String thePropertyName, @Nonnull String thePropertyValue) {
157 		return addProperty(TermConceptPropertyTypeEnum.STRING, thePropertyName, thePropertyValue);
158 	}
159 
160 	@Override
161 	public boolean equals(Object theObj) {
162 		if (!(theObj instanceof TermConcept)) {
163 			return false;
164 		}
165 		if (theObj == this) {
166 			return true;
167 		}
168 
169 		TermConcept obj = (TermConcept) theObj;
170 
171 		EqualsBuilder b = new EqualsBuilder();
172 		b.append(myCodeSystem, obj.myCodeSystem);
173 		b.append(myCode, obj.myCode);
174 		return b.isEquals();
175 	}
176 
177 	public Collection<TermConceptParentChildLink> getChildren() {
178 		if (myChildren == null) {
179 			myChildren = new ArrayList<>();
180 		}
181 		return myChildren;
182 	}
183 
184 	public String getCode() {
185 		return myCode;
186 	}
187 
188 	public void setCode(String theCode) {
189 		ValidateUtil.isNotBlankOrThrowInvalidRequest(theCode, "Code must not be null or empty");
190 		myCode = theCode;
191 	}
192 
193 	public TermCodeSystemVersion getCodeSystemVersion() {
194 		return myCodeSystem;
195 	}
196 
197 	public void setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) {
198 		myCodeSystem = theCodeSystemVersion;
199 		if (theCodeSystemVersion.getPid() != null) {
200 			myCodeSystemVersionPid = theCodeSystemVersion.getPid();
201 		}
202 	}
203 
204 	public List<Coding> getCodingProperties(String thePropertyName) {
205 		List<Coding> retVal = new ArrayList<>();
206 		for (TermConceptProperty next : getProperties()) {
207 			if (thePropertyName.equals(next.getKey())) {
208 				if (next.getType() == TermConceptPropertyTypeEnum.CODING) {
209 					Coding coding = new Coding();
210 					coding.setSystem(next.getCodeSystem());
211 					coding.setCode(next.getValue());
212 					coding.setDisplay(next.getDisplay());
213 					retVal.add(coding);
214 				}
215 			}
216 		}
217 		return retVal;
218 	}
219 
220 	public Collection<TermConceptDesignation> getDesignations() {
221 		if (myDesignations == null) {
222 			myDesignations = new ArrayList<>();
223 		}
224 		return myDesignations;
225 	}
226 
227 	public String getDisplay() {
228 		return myDisplay;
229 	}
230 
231 	public TermConcept setDisplay(String theDisplay) {
232 		myDisplay = theDisplay;
233 		if (isNotBlank(theDisplay) && theDisplay.length() > MAX_DESC_LENGTH) {
234 			myDisplay = myDisplay.substring(0, MAX_DESC_LENGTH);
235 		}
236 		return this;
237 	}
238 
239 	public Long getId() {
240 		return myId;
241 	}
242 
243 	public Long getIndexStatus() {
244 		return myIndexStatus;
245 	}
246 
247 	public void setIndexStatus(Long theIndexStatus) {
248 		myIndexStatus = theIndexStatus;
249 	}
250 
251 	public String getParentPidsAsString() {
252 		return myParentPids;
253 	}
254 
255 	public Collection<TermConceptParentChildLink> getParents() {
256 		if (myParents == null) {
257 			myParents = new ArrayList<>();
258 		}
259 		return myParents;
260 	}
261 
262 	public Collection<TermConceptProperty> getProperties() {
263 		if (myProperties == null) {
264 			myProperties = new ArrayList<>();
265 		}
266 		return myProperties;
267 	}
268 
269 	public Integer getSequence() {
270 		return mySequence;
271 	}
272 
273 	public void setSequence(Integer theSequence) {
274 		mySequence = theSequence;
275 	}
276 
277 	public List<String> getStringProperties(String thePropertyName) {
278 		List<String> retVal = new ArrayList<>();
279 		for (TermConceptProperty next : getProperties()) {
280 			if (thePropertyName.equals(next.getKey())) {
281 				if (next.getType() == TermConceptPropertyTypeEnum.STRING) {
282 					retVal.add(next.getValue());
283 				}
284 			}
285 		}
286 		return retVal;
287 	}
288 
289 	public String getStringProperty(String thePropertyName) {
290 		List<String> properties = getStringProperties(thePropertyName);
291 		if (properties.size() > 0) {
292 			return properties.get(0);
293 		}
294 		return null;
295 	}
296 
297 	@Override
298 	public int hashCode() {
299 		HashCodeBuilder b = new HashCodeBuilder();
300 		b.append(myCodeSystem);
301 		b.append(myCode);
302 		return b.toHashCode();
303 	}
304 
305 	private void parentPids(TermConcept theNextConcept, Set<Long> theParentPids) {
306 		for (TermConceptParentChildLink nextParentLink : theNextConcept.getParents()) {
307 			TermConcept parent = nextParentLink.getParent();
308 			if (parent != null) {
309 				Long parentConceptId = parent.getId();
310 				Validate.notNull(parentConceptId);
311 				if (theParentPids.add(parentConceptId)) {
312 					parentPids(parent, theParentPids);
313 				}
314 			}
315 		}
316 	}
317 
318 	@SuppressWarnings("unused")
319 	@PreUpdate
320 	@PrePersist
321 	public void prePersist() {
322 		if (myParentPids == null) {
323 			Set<Long> parentPids = new HashSet<>();
324 			TermConcept entity = this;
325 			parentPids(entity, parentPids);
326 			entity.setParentPids(parentPids);
327 
328 			ourLog.trace("Code {}/{} has parents {}", entity.getId(), entity.getCode(), entity.getParentPidsAsString());
329 		}
330 	}
331 
332 	private void setParentPids(Set<Long> theParentPids) {
333 		StringBuilder b = new StringBuilder();
334 		for (Long next : theParentPids) {
335 			if (b.length() > 0) {
336 				b.append(' ');
337 			}
338 			b.append(next);
339 		}
340 
341 		if (b.length() == 0) {
342 			b.append("NONE");
343 		}
344 
345 		myParentPids = b.toString();
346 	}
347 
348 	public void setParentPids(String theParentPids) {
349 		myParentPids = theParentPids;
350 	}
351 
352 	@Override
353 	public String toString() {
354 		return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("code", myCode).append("display", myDisplay).build();
355 	}
356 
357 	public List<IContextValidationSupport.BaseConceptProperty> toValidationProperties() {
358 		List<IContextValidationSupport.BaseConceptProperty> retVal = new ArrayList<>();
359 		for (TermConceptProperty next : getProperties()) {
360 			switch (next.getType()) {
361 				case STRING:
362 					retVal.add(new IContextValidationSupport.StringConceptProperty(next.getKey(), next.getValue()));
363 					break;
364 				case CODING:
365 					retVal.add(new IContextValidationSupport.CodingConceptProperty(next.getKey(), next.getCodeSystem(), next.getValue(), next.getDisplay()));
366 					break;
367 				default:
368 					throw new IllegalStateException("Don't know how to handle " + next.getType());
369 			}
370 		}
371 		return retVal;
372 	}
373 }