![](/hapi-fhir/images/logos/raccoon-forwards.png)
001/* 002 * #%L 003 * HAPI FHIR JPA Model 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.model.entity; 021 022import ca.uhn.fhir.interceptor.model.RequestPartitionId; 023import ca.uhn.fhir.jpa.model.config.PartitionSettings; 024import ca.uhn.fhir.jpa.model.listener.IndexStorageOptimizationListener; 025import ca.uhn.fhir.model.api.IQueryParameterType; 026import ca.uhn.fhir.rest.api.Constants; 027import ca.uhn.fhir.rest.param.TokenParam; 028import jakarta.persistence.Column; 029import jakarta.persistence.Embeddable; 030import jakarta.persistence.Entity; 031import jakarta.persistence.EntityListeners; 032import jakarta.persistence.FetchType; 033import jakarta.persistence.ForeignKey; 034import jakarta.persistence.GeneratedValue; 035import jakarta.persistence.GenerationType; 036import jakarta.persistence.Id; 037import jakarta.persistence.Index; 038import jakarta.persistence.JoinColumn; 039import jakarta.persistence.ManyToOne; 040import jakarta.persistence.PrePersist; 041import jakarta.persistence.PreUpdate; 042import jakarta.persistence.SequenceGenerator; 043import jakarta.persistence.Table; 044import org.apache.commons.lang3.StringUtils; 045import org.apache.commons.lang3.builder.EqualsBuilder; 046import org.apache.commons.lang3.builder.HashCodeBuilder; 047import org.apache.commons.lang3.builder.ToStringBuilder; 048import org.apache.commons.lang3.builder.ToStringStyle; 049import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; 050 051import static ca.uhn.fhir.jpa.model.util.SearchParamHash.hashSearchParam; 052import static org.apache.commons.lang3.StringUtils.defaultString; 053import static org.apache.commons.lang3.StringUtils.trim; 054 055@Embeddable 056@EntityListeners(IndexStorageOptimizationListener.class) 057@Entity 058@Table( 059 name = "HFJ_SPIDX_TOKEN", 060 indexes = { 061 /* 062 * Note: We previously had indexes with the following names, 063 * do not reuse these names: 064 * IDX_SP_TOKEN 065 * IDX_SP_TOKEN_UNQUAL 066 */ 067 068 @Index(name = "IDX_SP_TOKEN_HASH_V2", columnList = "HASH_IDENTITY,SP_SYSTEM,SP_VALUE,RES_ID,PARTITION_ID"), 069 @Index(name = "IDX_SP_TOKEN_HASH_S_V2", columnList = "HASH_SYS,RES_ID,PARTITION_ID"), 070 @Index(name = "IDX_SP_TOKEN_HASH_SV_V2", columnList = "HASH_SYS_AND_VALUE,RES_ID,PARTITION_ID"), 071 @Index(name = "IDX_SP_TOKEN_HASH_V_V2", columnList = "HASH_VALUE,RES_ID,PARTITION_ID"), 072 @Index( 073 name = "IDX_SP_TOKEN_RESID_V2", 074 columnList = "RES_ID,HASH_SYS_AND_VALUE,HASH_VALUE,HASH_SYS,HASH_IDENTITY,PARTITION_ID") 075 }) 076public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchParam { 077 078 public static final int MAX_LENGTH = 200; 079 080 private static final long serialVersionUID = 1L; 081 082 @FullTextField 083 @Column(name = "SP_SYSTEM", nullable = true, length = MAX_LENGTH) 084 public String mySystem; 085 086 @FullTextField 087 @Column(name = "SP_VALUE", nullable = true, length = MAX_LENGTH) 088 private String myValue; 089 090 @SuppressWarnings("unused") 091 @Id 092 @SequenceGenerator(name = "SEQ_SPIDX_TOKEN", sequenceName = "SEQ_SPIDX_TOKEN") 093 @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_TOKEN") 094 @Column(name = "SP_ID") 095 private Long myId; 096 /** 097 * @since 3.4.0 - At some point this should be made not-null 098 */ 099 @Column(name = "HASH_SYS", nullable = true) 100 private Long myHashSystem; 101 /** 102 * @since 3.4.0 - At some point this should be made not-null 103 */ 104 @Column(name = "HASH_SYS_AND_VALUE", nullable = true) 105 private Long myHashSystemAndValue; 106 /** 107 * @since 3.4.0 - At some point this should be made not-null 108 */ 109 @Column(name = "HASH_VALUE", nullable = true) 110 private Long myHashValue; 111 112 @ManyToOne( 113 optional = false, 114 fetch = FetchType.LAZY, 115 cascade = {}) 116 @JoinColumn( 117 foreignKey = @ForeignKey(name = "FK_SP_TOKEN_RES"), 118 name = "RES_ID", 119 referencedColumnName = "RES_ID", 120 nullable = false) 121 private ResourceTable myResource; 122 123 /** 124 * Constructor 125 */ 126 public ResourceIndexedSearchParamToken() { 127 super(); 128 } 129 130 /** 131 * Constructor 132 */ 133 public ResourceIndexedSearchParamToken( 134 PartitionSettings thePartitionSettings, 135 String theResourceType, 136 String theParamName, 137 String theSystem, 138 String theValue) { 139 super(); 140 setPartitionSettings(thePartitionSettings); 141 setResourceType(theResourceType); 142 setParamName(theParamName); 143 setSystem(theSystem); 144 setValue(theValue); 145 calculateHashes(); 146 } 147 148 /** 149 * Constructor 150 */ 151 public ResourceIndexedSearchParamToken( 152 PartitionSettings thePartitionSettings, String theResourceType, String theParamName, boolean theMissing) { 153 super(); 154 setPartitionSettings(thePartitionSettings); 155 setResourceType(theResourceType); 156 setParamName(theParamName); 157 setMissing(theMissing); 158 calculateHashes(); 159 } 160 161 @Override 162 public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) { 163 super.copyMutableValuesFrom(theSource); 164 ResourceIndexedSearchParamToken source = (ResourceIndexedSearchParamToken) theSource; 165 166 mySystem = source.mySystem; 167 myValue = source.myValue; 168 myHashSystem = source.myHashSystem; 169 myHashSystemAndValue = source.getHashSystemAndValue(); 170 myHashValue = source.myHashValue; 171 myHashIdentity = source.myHashIdentity; 172 } 173 174 @Override 175 public void clearHashes() { 176 myHashIdentity = null; 177 myHashSystem = null; 178 myHashSystemAndValue = null; 179 myHashValue = null; 180 } 181 182 @Override 183 public void calculateHashes() { 184 if (myHashIdentity != null || myHashSystem != null || myHashValue != null || myHashSystemAndValue != null) { 185 return; 186 } 187 188 String resourceType = getResourceType(); 189 String paramName = getParamName(); 190 String system = getSystem(); 191 String value = getValue(); 192 setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); 193 setHashSystemAndValue(calculateHashSystemAndValue( 194 getPartitionSettings(), getPartitionId(), resourceType, paramName, system, value)); 195 196 // Searches using the :of-type modifier can never be partial (system-only or value-only) so don't 197 // bother saving these 198 boolean calculatePartialHashes = !StringUtils.endsWith(paramName, Constants.PARAMQUALIFIER_TOKEN_OF_TYPE); 199 if (calculatePartialHashes) { 200 setHashSystem( 201 calculateHashSystem(getPartitionSettings(), getPartitionId(), resourceType, paramName, system)); 202 setHashValue(calculateHashValue(getPartitionSettings(), getPartitionId(), resourceType, paramName, value)); 203 } 204 } 205 206 @Override 207 public boolean equals(Object theObj) { 208 if (this == theObj) { 209 return true; 210 } 211 if (theObj == null) { 212 return false; 213 } 214 if (!(theObj instanceof ResourceIndexedSearchParamToken)) { 215 return false; 216 } 217 ResourceIndexedSearchParamToken obj = (ResourceIndexedSearchParamToken) theObj; 218 EqualsBuilder b = new EqualsBuilder(); 219 b.append(getHashIdentity(), obj.getHashIdentity()); 220 b.append(getHashSystem(), obj.getHashSystem()); 221 b.append(getHashValue(), obj.getHashValue()); 222 b.append(getHashSystemAndValue(), obj.getHashSystemAndValue()); 223 b.append(isMissing(), obj.isMissing()); 224 return b.isEquals(); 225 } 226 227 public Long getHashSystem() { 228 return myHashSystem; 229 } 230 231 private void setHashSystem(Long theHashSystem) { 232 myHashSystem = theHashSystem; 233 } 234 235 public Long getHashSystemAndValue() { 236 return myHashSystemAndValue; 237 } 238 239 private void setHashSystemAndValue(Long theHashSystemAndValue) { 240 myHashSystemAndValue = theHashSystemAndValue; 241 } 242 243 public Long getHashValue() { 244 return myHashValue; 245 } 246 247 private void setHashValue(Long theHashValue) { 248 myHashValue = theHashValue; 249 } 250 251 @Override 252 public Long getId() { 253 return myId; 254 } 255 256 @Override 257 public void setId(Long theId) { 258 myId = theId; 259 } 260 261 public String getSystem() { 262 return mySystem; 263 } 264 265 public void setSystem(String theSystem) { 266 mySystem = StringUtils.defaultIfBlank(theSystem, null); 267 myHashSystemAndValue = null; 268 } 269 270 public String getValue() { 271 return myValue; 272 } 273 274 public ResourceIndexedSearchParamToken setValue(String theValue) { 275 myValue = StringUtils.defaultIfBlank(theValue, null); 276 myHashSystemAndValue = null; 277 return this; 278 } 279 280 @Override 281 public int hashCode() { 282 HashCodeBuilder b = new HashCodeBuilder(); 283 b.append(getHashIdentity()); 284 b.append(getHashValue()); 285 b.append(getHashSystem()); 286 b.append(getHashSystemAndValue()); 287 b.append(isMissing()); 288 return b.toHashCode(); 289 } 290 291 @Override 292 public IQueryParameterType toQueryParameterType() { 293 return new TokenParam(getSystem(), getValue()); 294 } 295 296 @Override 297 public String toString() { 298 ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); 299 b.append("id", getId()); 300 if (getPartitionId() != null) { 301 b.append("partitionId", getPartitionId().getPartitionId()); 302 } 303 b.append("resourceType", getResourceType()); 304 b.append("paramName", getParamName()); 305 if (isMissing()) { 306 b.append("missing", true); 307 } else { 308 b.append("system", getSystem()); 309 b.append("value", getValue()); 310 } 311 b.append("hashIdentity", myHashIdentity); 312 b.append("hashSystem", myHashSystem); 313 b.append("hashValue", myHashValue); 314 b.append("hashSysAndValue", myHashSystemAndValue); 315 b.append("partition", getPartitionId()); 316 return b.build(); 317 } 318 319 @Override 320 public boolean matches(IQueryParameterType theParam) { 321 if (!(theParam instanceof TokenParam)) { 322 return false; 323 } 324 TokenParam token = (TokenParam) theParam; 325 boolean retVal = false; 326 String valueString = defaultString(getValue()); 327 String tokenValueString = defaultString(token.getValue()); 328 329 // Only match on system if it wasn't specified 330 if (token.getSystem() == null || token.getSystem().isEmpty()) { 331 if (valueString.equalsIgnoreCase(tokenValueString)) { 332 retVal = true; 333 } 334 } else if (tokenValueString == null || tokenValueString.isEmpty()) { 335 if (token.getSystem().equalsIgnoreCase(getSystem())) { 336 retVal = true; 337 } 338 } else { 339 if (token.getSystem().equalsIgnoreCase(getSystem()) && valueString.equalsIgnoreCase(tokenValueString)) { 340 retVal = true; 341 } 342 } 343 return retVal; 344 } 345 346 public static long calculateHashSystem( 347 PartitionSettings thePartitionSettings, 348 PartitionablePartitionId theRequestPartitionId, 349 String theResourceType, 350 String theParamName, 351 String theSystem) { 352 RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId); 353 return calculateHashSystem(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theSystem); 354 } 355 356 public static long calculateHashSystem( 357 PartitionSettings thePartitionSettings, 358 RequestPartitionId theRequestPartitionId, 359 String theResourceType, 360 String theParamName, 361 String theSystem) { 362 return hashSearchParam( 363 thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, trim(theSystem)); 364 } 365 366 public static long calculateHashSystemAndValue( 367 PartitionSettings thePartitionSettings, 368 PartitionablePartitionId theRequestPartitionId, 369 String theResourceType, 370 String theParamName, 371 String theSystem, 372 String theValue) { 373 RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId); 374 return calculateHashSystemAndValue( 375 thePartitionSettings, requestPartitionId, theResourceType, theParamName, theSystem, theValue); 376 } 377 378 public static long calculateHashSystemAndValue( 379 PartitionSettings thePartitionSettings, 380 RequestPartitionId theRequestPartitionId, 381 String theResourceType, 382 String theParamName, 383 String theSystem, 384 String theValue) { 385 return hashSearchParam( 386 thePartitionSettings, 387 theRequestPartitionId, 388 theResourceType, 389 theParamName, 390 defaultString(trim(theSystem)), 391 trim(theValue)); 392 } 393 394 public static long calculateHashValue( 395 PartitionSettings thePartitionSettings, 396 PartitionablePartitionId theRequestPartitionId, 397 String theResourceType, 398 String theParamName, 399 String theValue) { 400 RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId); 401 return calculateHashValue(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theValue); 402 } 403 404 public static long calculateHashValue( 405 PartitionSettings thePartitionSettings, 406 RequestPartitionId theRequestPartitionId, 407 String theResourceType, 408 String theParamName, 409 String theValue) { 410 String value = trim(theValue); 411 return hashSearchParam(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value); 412 } 413 414 @Override 415 public ResourceTable getResource() { 416 return myResource; 417 } 418 419 @Override 420 public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) { 421 myResource = theResource; 422 setResourceType(theResource.getResourceType()); 423 return this; 424 } 425 426 /** 427 * We truncate the fields at the last moment because the tables have limited size. 428 * We don't truncate earlier in the flow because the index hashes MUST be calculated on the full string. 429 */ 430 @PrePersist 431 @PreUpdate 432 public void truncateFieldsForDB() { 433 mySystem = StringUtils.truncate(mySystem, MAX_LENGTH); 434 myValue = StringUtils.truncate(myValue, MAX_LENGTH); 435 } 436}