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