
001/* 002 * #%L 003 * HAPI FHIR JPA Model 004 * %% 005 * Copyright (C) 2014 - 2023 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.i18n.Msg; 023import ca.uhn.fhir.interceptor.model.RequestPartitionId; 024import ca.uhn.fhir.jpa.model.config.PartitionSettings; 025import ca.uhn.fhir.model.api.IQueryParameterType; 026import ca.uhn.fhir.rest.param.StringParam; 027import ca.uhn.fhir.util.StringUtil; 028import org.apache.commons.lang3.builder.EqualsBuilder; 029import org.apache.commons.lang3.builder.HashCodeBuilder; 030import org.apache.commons.lang3.builder.ToStringBuilder; 031import org.apache.commons.lang3.builder.ToStringStyle; 032 033import javax.persistence.Column; 034import javax.persistence.Embeddable; 035import javax.persistence.Entity; 036import javax.persistence.ForeignKey; 037import javax.persistence.GeneratedValue; 038import javax.persistence.GenerationType; 039import javax.persistence.Id; 040import javax.persistence.Index; 041import javax.persistence.JoinColumn; 042import javax.persistence.ManyToOne; 043import javax.persistence.SequenceGenerator; 044import javax.persistence.Table; 045 046import static org.apache.commons.lang3.StringUtils.defaultString; 047 048//@formatter:off 049@Embeddable 050@Entity 051@Table(name = "HFJ_SPIDX_STRING", indexes = { 052 /* 053 * Note: We previously had indexes with the following names, 054 * do not reuse these names: 055 * IDX_SP_STRING 056 */ 057 058 // This is used for sorting, and for :contains queries currently 059 @Index(name = "IDX_SP_STRING_HASH_IDENT_V2", columnList = "HASH_IDENTITY,RES_ID,PARTITION_ID"), 060 061 @Index(name = "IDX_SP_STRING_HASH_NRM_V2", columnList = "HASH_NORM_PREFIX,SP_VALUE_NORMALIZED,RES_ID,PARTITION_ID"), 062 @Index(name = "IDX_SP_STRING_HASH_EXCT_V2", columnList = "HASH_EXACT,RES_ID,PARTITION_ID"), 063 064 @Index(name = "IDX_SP_STRING_RESID_V2", columnList = "RES_ID,HASH_NORM_PREFIX,PARTITION_ID") 065}) 066public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchParam { 067 068 /* 069 * Note that MYSQL chokes on unique indexes for lengths > 255 so be careful here 070 */ 071 public static final int MAX_LENGTH = 200; 072 public static final int HASH_PREFIX_LENGTH = 1; 073 private static final long serialVersionUID = 1L; 074 @Id 075 @SequenceGenerator(name = "SEQ_SPIDX_STRING", sequenceName = "SEQ_SPIDX_STRING") 076 @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_STRING") 077 @Column(name = "SP_ID") 078 private Long myId; 079 080 @ManyToOne(optional = false) 081 @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, 082 foreignKey = @ForeignKey(name = "FK_SPIDXSTR_RESOURCE")) 083 private ResourceTable myResource; 084 085 @Column(name = "SP_VALUE_EXACT", length = MAX_LENGTH, nullable = true) 086 private String myValueExact; 087 088 @Column(name = "SP_VALUE_NORMALIZED", length = MAX_LENGTH, nullable = true) 089 private String myValueNormalized; 090 /** 091 * @since 3.4.0 - At some point this should be made not-null 092 */ 093 @Column(name = "HASH_NORM_PREFIX", nullable = true) 094 private Long myHashNormalizedPrefix; 095 /** 096 * @since 3.6.0 - At some point this should be made not-null 097 */ 098 @Column(name = "HASH_IDENTITY", nullable = true) 099 private Long myHashIdentity; 100 /** 101 * @since 3.4.0 - At some point this should be made not-null 102 */ 103 @Column(name = "HASH_EXACT", nullable = true) 104 private Long myHashExact; 105 106 public ResourceIndexedSearchParamString() { 107 super(); 108 } 109 110 public ResourceIndexedSearchParamString(PartitionSettings thePartitionSettings, StorageSettings theStorageSettings, String theResourceType, String theParamName, String theValueNormalized, String theValueExact) { 111 setPartitionSettings(thePartitionSettings); 112 setStorageSettings(theStorageSettings); 113 setResourceType(theResourceType); 114 setParamName(theParamName); 115 setValueNormalized(theValueNormalized); 116 setValueExact(theValueExact); 117 calculateHashes(); 118 } 119 120 @Override 121 public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) { 122 super.copyMutableValuesFrom(theSource); 123 ResourceIndexedSearchParamString source = (ResourceIndexedSearchParamString) theSource; 124 myValueExact = source.myValueExact; 125 myValueNormalized = source.myValueNormalized; 126 myHashExact = source.myHashExact; 127 myHashIdentity = source.myHashIdentity; 128 myHashNormalizedPrefix = source.myHashNormalizedPrefix; 129 } 130 131 @Override 132 public void clearHashes() { 133 myHashIdentity = null; 134 myHashNormalizedPrefix = null; 135 myHashExact = null; 136 } 137 138 139 @Override 140 public void calculateHashes() { 141 if (myHashIdentity != null || myHashExact != null || myHashNormalizedPrefix != null) { 142 return; 143 } 144 145 String resourceType = getResourceType(); 146 String paramName = getParamName(); 147 String valueNormalized = getValueNormalized(); 148 String valueExact = getValueExact(); 149 setHashNormalizedPrefix(calculateHashNormalized(getPartitionSettings(), getPartitionId(), getStorageSettings(), resourceType, paramName, valueNormalized)); 150 setHashExact(calculateHashExact(getPartitionSettings(), getPartitionId(), resourceType, paramName, valueExact)); 151 setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); 152 } 153 154 @Override 155 public boolean equals(Object theObj) { 156 if (this == theObj) { 157 return true; 158 } 159 if (theObj == null) { 160 return false; 161 } 162 if (!(theObj instanceof ResourceIndexedSearchParamString)) { 163 return false; 164 } 165 ResourceIndexedSearchParamString obj = (ResourceIndexedSearchParamString) theObj; 166 EqualsBuilder b = new EqualsBuilder(); 167 b.append(getResourceType(), obj.getResourceType()); 168 b.append(getParamName(), obj.getParamName()); 169 b.append(getValueExact(), obj.getValueExact()); 170 b.append(getHashIdentity(), obj.getHashIdentity()); 171 b.append(getHashExact(), obj.getHashExact()); 172 b.append(getHashNormalizedPrefix(), obj.getHashNormalizedPrefix()); 173 b.append(getValueNormalized(), obj.getValueNormalized()); 174 return b.isEquals(); 175 } 176 177 private Long getHashIdentity() { 178 return myHashIdentity; 179 } 180 181 public void setHashIdentity(Long theHashIdentity) { 182 myHashIdentity = theHashIdentity; 183 } 184 185 public Long getHashExact() { 186 return myHashExact; 187 } 188 189 public void setHashExact(Long theHashExact) { 190 myHashExact = theHashExact; 191 } 192 193 public Long getHashNormalizedPrefix() { 194 return myHashNormalizedPrefix; 195 } 196 197 public void setHashNormalizedPrefix(Long theHashNormalizedPrefix) { 198 myHashNormalizedPrefix = theHashNormalizedPrefix; 199 } 200 201 @Override 202 public Long getId() { 203 return myId; 204 } 205 206 @Override 207 public void setId(Long theId) { 208 myId = theId; 209 } 210 211 212 public String getValueExact() { 213 return myValueExact; 214 } 215 216 public ResourceIndexedSearchParamString setValueExact(String theValueExact) { 217 if (defaultString(theValueExact).length() > MAX_LENGTH) { 218 throw new IllegalArgumentException(Msg.code(1529) + "Value is too long: " + theValueExact.length()); 219 } 220 myValueExact = theValueExact; 221 return this; 222 } 223 224 public String getValueNormalized() { 225 return myValueNormalized; 226 } 227 228 public ResourceIndexedSearchParamString setValueNormalized(String theValueNormalized) { 229 if (defaultString(theValueNormalized).length() > MAX_LENGTH) { 230 throw new IllegalArgumentException(Msg.code(1530) + "Value is too long: " + theValueNormalized.length()); 231 } 232 myValueNormalized = theValueNormalized; 233 return this; 234 } 235 236 @Override 237 public int hashCode() { 238 HashCodeBuilder b = new HashCodeBuilder(); 239 b.append(getResourceType()); 240 b.append(getParamName()); 241 b.append(getValueExact()); 242 b.append(getHashIdentity()); 243 b.append(getHashExact()); 244 b.append(getHashNormalizedPrefix()); 245 b.append(getValueNormalized()); 246 return b.toHashCode(); 247 } 248 249 @Override 250 public IQueryParameterType toQueryParameterType() { 251 return new StringParam(getValueExact()); 252 } 253 254 @Override 255 public String toString() { 256 ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); 257 b.append("resourceType", getResourceType()); 258 b.append("paramName", getParamName()); 259 b.append("resourceId", getResourcePid()); 260 b.append("hashIdentity", getHashIdentity()); 261 b.append("hashNormalizedPrefix", getHashNormalizedPrefix()); 262 b.append("valueNormalized", getValueNormalized()); 263 b.append("partitionId", getPartitionId()); 264 return b.build(); 265 } 266 267 @Override 268 public boolean matches(IQueryParameterType theParam) { 269 if (!(theParam instanceof StringParam)) { 270 return false; 271 } 272 StringParam string = (StringParam) theParam; 273 String normalizedString = StringUtil.normalizeStringForSearchIndexing(defaultString(string.getValue())); 274 return defaultString(getValueNormalized()).startsWith(normalizedString); 275 } 276 277 public static long calculateHashExact(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValueExact) { 278 RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId); 279 return calculateHashExact(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theValueExact); 280 } 281 282 public static long calculateHashExact(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValueExact) { 283 return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theValueExact); 284 } 285 286 public static long calculateHashNormalized(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, StorageSettings theStorageSettings, String theResourceType, String theParamName, String theValueNormalized) { 287 RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId); 288 return calculateHashNormalized(thePartitionSettings, requestPartitionId, theStorageSettings, theResourceType, theParamName, theValueNormalized); 289 } 290 291 public static long calculateHashNormalized(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, StorageSettings theStorageSettings, String theResourceType, String theParamName, String theValueNormalized) { 292 /* 293 * If we're not allowing contained searches, we'll add the first 294 * bit of the normalized value to the hash. This helps to 295 * make the hash even more unique, which will be good for 296 * performance. 297 */ 298 int hashPrefixLength = HASH_PREFIX_LENGTH; 299 if (theStorageSettings.isAllowContainsSearches()) { 300 hashPrefixLength = 0; 301 } 302 303 String value = StringUtil.left(theValueNormalized, hashPrefixLength); 304 return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value); 305 } 306 307 @Override 308 public ResourceTable getResource() { 309 return myResource; 310 } 311 312 @Override 313 public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) { 314 myResource = theResource; 315 setResourceType(theResource.getResourceType()); 316 return this; 317 } 318}