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  	@Index(name = "IDX_CONCEPT_UPDATED", columnList = "CONCEPT_UPDATED")
50  })
51  public class TermConcept implements Serializable {
52  	public static final int CODE_LENGTH = 500;
53  	protected static final int MAX_DESC_LENGTH = 400;
54  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TermConcept.class);
55  
56  	private static final long serialVersionUID = 1L;
57  
58  	@OneToMany(fetch = FetchType.LAZY, mappedBy = "myParent", cascade = {})
59  	private Collection<TermConceptParentChildLink> myChildren;
60  
61  	@Column(name = "CODE", length = CODE_LENGTH, nullable = false)
62  	@Fields({@Field(name = "myCode", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "exactAnalyzer")),})
63  	private String myCode;
64  	@Temporal(TemporalType.TIMESTAMP)
65  	@Column(name = "CONCEPT_UPDATED", nullable = true)
66  	private Date myUpdated;
67  	@ManyToOne()
68  	@JoinColumn(name = "CODESYSTEM_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPT_PID_CS_PID"))
69  	private TermCodeSystemVersion myCodeSystem;
70  	@Column(name = "CODESYSTEM_PID", insertable = false, updatable = false)
71  	@Fields({@Field(name = "myCodeSystemVersionPid")})
72  	private long myCodeSystemVersionPid;
73  	@Column(name = "DISPLAY", length = MAX_DESC_LENGTH, nullable = true)
74  	@Fields({
75  		@Field(name = "myDisplay", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")),
76  		@Field(name = "myDisplayEdgeNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteEdgeAnalyzer")),
77  		@Field(name = "myDisplayNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteNGramAnalyzer")),
78  		@Field(name = "myDisplayPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer"))
79  	})
80  	private String myDisplay;
81  	@OneToMany(mappedBy = "myConcept", orphanRemoval = false)
82  	@Field(name = "PROPmyProperties", analyzer = @Analyzer(definition = "termConceptPropertyAnalyzer"))
83  	@FieldBridge(impl = TermConceptPropertyFieldBridge.class)
84  	private Collection<TermConceptProperty> myProperties;
85  	@OneToMany(mappedBy = "myConcept", orphanRemoval = false)
86  	private Collection<TermConceptDesignation> myDesignations;
87  	@Id()
88  	@SequenceGenerator(name = "SEQ_CONCEPT_PID", sequenceName = "SEQ_CONCEPT_PID")
89  	@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PID")
90  	@Column(name = "PID")
91  	private Long myId;
92  	@Column(name = "INDEX_STATUS", nullable = true)
93  	private Long myIndexStatus;
94  	@Field(name = "myParentPids", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "conceptParentPidsAnalyzer"))
95  	@Lob
96  	@Column(name = "PARENT_PIDS", nullable = true)
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 		designation.setCodeSystemVersion(myCodeSystem);
134 		getDesignations().add(designation);
135 		return designation;
136 	}
137 
138 	private TermConceptProperty addProperty(@Nonnull TermConceptPropertyTypeEnum thePropertyType, @Nonnull String thePropertyName, @Nonnull String thePropertyValue) {
139 		Validate.notBlank(thePropertyName);
140 
141 		TermConceptProperty property = new TermConceptProperty();
142 		property.setConcept(this);
143 		property.setCodeSystemVersion(myCodeSystem);
144 		property.setType(thePropertyType);
145 		property.setKey(thePropertyName);
146 		property.setValue(thePropertyValue);
147 		getProperties().add(property);
148 
149 		return property;
150 	}
151 
152 	public TermConceptProperty addPropertyCoding(@Nonnull String thePropertyName, @Nonnull String thePropertyCodeSystem, @Nonnull String thePropertyCode, String theDisplayName) {
153 		return addProperty(TermConceptPropertyTypeEnum.CODING, thePropertyName, thePropertyCode)
154 			.setCodeSystem(thePropertyCodeSystem)
155 			.setDisplay(theDisplayName);
156 	}
157 
158 	public TermConceptProperty addPropertyString(@Nonnull String thePropertyName, @Nonnull String thePropertyValue) {
159 		return addProperty(TermConceptPropertyTypeEnum.STRING, thePropertyName, thePropertyValue);
160 	}
161 
162 	@Override
163 	public boolean equals(Object theObj) {
164 		if (!(theObj instanceof TermConcept)) {
165 			return false;
166 		}
167 		if (theObj == this) {
168 			return true;
169 		}
170 
171 		TermConcept obj = (TermConcept) theObj;
172 
173 		EqualsBuilder b = new EqualsBuilder();
174 		b.append(myCodeSystem, obj.myCodeSystem);
175 		b.append(myCode, obj.myCode);
176 		return b.isEquals();
177 	}
178 
179 	public Collection<TermConceptParentChildLink> getChildren() {
180 		if (myChildren == null) {
181 			myChildren = new ArrayList<>();
182 		}
183 		return myChildren;
184 	}
185 
186 	public String getCode() {
187 		return myCode;
188 	}
189 
190 	public void setCode(String theCode) {
191 		ValidateUtil.isNotBlankOrThrowInvalidRequest(theCode, "Code must not be null or empty");
192 		myCode = theCode;
193 	}
194 
195 	public TermCodeSystemVersion getCodeSystemVersion() {
196 		return myCodeSystem;
197 	}
198 
199 	public void setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) {
200 		myCodeSystem = theCodeSystemVersion;
201 		if (theCodeSystemVersion.getPid() != null) {
202 			myCodeSystemVersionPid = theCodeSystemVersion.getPid();
203 		}
204 	}
205 
206 	public List<Coding> getCodingProperties(String thePropertyName) {
207 		List<Coding> retVal = new ArrayList<>();
208 		for (TermConceptProperty next : getProperties()) {
209 			if (thePropertyName.equals(next.getKey())) {
210 				if (next.getType() == TermConceptPropertyTypeEnum.CODING) {
211 					Coding coding = new Coding();
212 					coding.setSystem(next.getCodeSystem());
213 					coding.setCode(next.getValue());
214 					coding.setDisplay(next.getDisplay());
215 					retVal.add(coding);
216 				}
217 			}
218 		}
219 		return retVal;
220 	}
221 
222 	public Collection<TermConceptDesignation> getDesignations() {
223 		if (myDesignations == null) {
224 			myDesignations = new ArrayList<>();
225 		}
226 		return myDesignations;
227 	}
228 
229 	public String getDisplay() {
230 		return myDisplay;
231 	}
232 
233 	public TermConcept setDisplay(String theDisplay) {
234 		myDisplay = theDisplay;
235 		if (isNotBlank(theDisplay) && theDisplay.length() > MAX_DESC_LENGTH) {
236 			myDisplay = myDisplay.substring(0, MAX_DESC_LENGTH);
237 		}
238 		return this;
239 	}
240 
241 	public Long getId() {
242 		return myId;
243 	}
244 
245 	public Long getIndexStatus() {
246 		return myIndexStatus;
247 	}
248 
249 	public void setIndexStatus(Long theIndexStatus) {
250 		myIndexStatus = theIndexStatus;
251 	}
252 
253 	public String getParentPidsAsString() {
254 		return myParentPids;
255 	}
256 
257 	public Collection<TermConceptParentChildLink> getParents() {
258 		if (myParents == null) {
259 			myParents = new ArrayList<>();
260 		}
261 		return myParents;
262 	}
263 
264 	public Collection<TermConceptProperty> getProperties() {
265 		if (myProperties == null) {
266 			myProperties = new ArrayList<>();
267 		}
268 		return myProperties;
269 	}
270 
271 	public Integer getSequence() {
272 		return mySequence;
273 	}
274 
275 	public void setSequence(Integer theSequence) {
276 		mySequence = theSequence;
277 	}
278 
279 	public List<String> getStringProperties(String thePropertyName) {
280 		List<String> retVal = new ArrayList<>();
281 		for (TermConceptProperty next : getProperties()) {
282 			if (thePropertyName.equals(next.getKey())) {
283 				if (next.getType() == TermConceptPropertyTypeEnum.STRING) {
284 					retVal.add(next.getValue());
285 				}
286 			}
287 		}
288 		return retVal;
289 	}
290 
291 	public String getStringProperty(String thePropertyName) {
292 		List<String> properties = getStringProperties(thePropertyName);
293 		if (properties.size() > 0) {
294 			return properties.get(0);
295 		}
296 		return null;
297 	}
298 
299 	public Date getUpdated() {
300 		return myUpdated;
301 	}
302 
303 	public void setUpdated(Date theUpdated) {
304 		myUpdated = theUpdated;
305 	}
306 
307 	@Override
308 	public int hashCode() {
309 		HashCodeBuilder b = new HashCodeBuilder();
310 		b.append(myCodeSystem);
311 		b.append(myCode);
312 		return b.toHashCode();
313 	}
314 
315 	private void parentPids(TermConcept theNextConcept, Set<Long> theParentPids) {
316 		for (TermConceptParentChildLink nextParentLink : theNextConcept.getParents()) {
317 			TermConcept parent = nextParentLink.getParent();
318 			if (parent != null) {
319 				Long parentConceptId = parent.getId();
320 				Validate.notNull(parentConceptId);
321 				if (theParentPids.add(parentConceptId)) {
322 					parentPids(parent, theParentPids);
323 				}
324 			}
325 		}
326 	}
327 
328 	@SuppressWarnings("unused")
329 	@PreUpdate
330 	@PrePersist
331 	public void prePersist() {
332 		if (myParentPids == null) {
333 			Set<Long> parentPids = new HashSet<>();
334 			TermConcept entity = this;
335 			parentPids(entity, parentPids);
336 			entity.setParentPids(parentPids);
337 
338 			ourLog.trace("Code {}/{} has parents {}", entity.getId(), entity.getCode(), entity.getParentPidsAsString());
339 		}
340 	}
341 
342 	private void setParentPids(Set<Long> theParentPids) {
343 		StringBuilder b = new StringBuilder();
344 		for (Long next : theParentPids) {
345 			if (b.length() > 0) {
346 				b.append(' ');
347 			}
348 			b.append(next);
349 		}
350 
351 		if (b.length() == 0) {
352 			b.append("NONE");
353 		}
354 
355 		myParentPids = b.toString();
356 	}
357 
358 	public void setParentPids(String theParentPids) {
359 		myParentPids = theParentPids;
360 	}
361 
362 	@Override
363 	public String toString() {
364 		return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("code", myCode).append("display", myDisplay).build();
365 	}
366 
367 	public List<IContextValidationSupport.BaseConceptProperty> toValidationProperties() {
368 		List<IContextValidationSupport.BaseConceptProperty> retVal = new ArrayList<>();
369 		for (TermConceptProperty next : getProperties()) {
370 			switch (next.getType()) {
371 				case STRING:
372 					retVal.add(new IContextValidationSupport.StringConceptProperty(next.getKey(), next.getValue()));
373 					break;
374 				case CODING:
375 					retVal.add(new IContextValidationSupport.CodingConceptProperty(next.getKey(), next.getCodeSystem(), next.getValue(), next.getDisplay()));
376 					break;
377 				default:
378 					throw new IllegalStateException("Don't know how to handle " + next.getType());
379 			}
380 		}
381 		return retVal;
382 	}
383 }