001package ca.uhn.fhir.jpa.model.entity; 002 003/* 004 * #%L 005 * HAPI FHIR JPA Model 006 * %% 007 * Copyright (C) 2014 - 2022 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", columnList = "HASH_NORM_PREFIX,SP_VALUE_NORMALIZED"), 063 @Index(name = "IDX_SP_STRING_HASH_EXCT", columnList = "HASH_EXACT"), 064 065 @Index(name = "IDX_SP_STRING_UPDATED", columnList = "SP_UPDATED"), 066 @Index(name = "IDX_SP_STRING_RESID", columnList = "RES_ID") 067}) 068public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchParam { 069 070 /* 071 * Note that MYSQL chokes on unique indexes for lengths > 255 so be careful here 072 */ 073 public static final int MAX_LENGTH = 200; 074 public static final int HASH_PREFIX_LENGTH = 1; 075 private static final long serialVersionUID = 1L; 076 @Id 077 @SequenceGenerator(name = "SEQ_SPIDX_STRING", sequenceName = "SEQ_SPIDX_STRING") 078 @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_STRING") 079 @Column(name = "SP_ID") 080 private Long myId; 081 082 @ManyToOne(optional = false) 083 @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", insertable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_SPIDXSTR_RESOURCE")) 084 private ResourceTable myResourceTable; 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 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 return b.toHashCode(); 243 } 244 245 @Override 246 public IQueryParameterType toQueryParameterType() { 247 return new StringParam(getValueExact()); 248 } 249 250 @Override 251 public String toString() { 252 ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); 253 b.append("paramName", getParamName()); 254 b.append("resourceId", getResourcePid()); 255 b.append("hashNormalizedPrefix", getHashNormalizedPrefix()); 256 b.append("valueNormalized", getValueNormalized()); 257 b.append("partitionId", getPartitionId()); 258 return b.build(); 259 } 260 261 @Override 262 public boolean matches(IQueryParameterType theParam) { 263 if (!(theParam instanceof StringParam)) { 264 return false; 265 } 266 StringParam string = (StringParam) theParam; 267 String normalizedString = StringUtil.normalizeStringForSearchIndexing(defaultString(string.getValue())); 268 return defaultString(getValueNormalized()).startsWith(normalizedString); 269 } 270 271 public static long calculateHashExact(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValueExact) { 272 RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId); 273 return calculateHashExact(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theValueExact); 274 } 275 276 public static long calculateHashExact(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValueExact) { 277 return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theValueExact); 278 } 279 280 public static long calculateHashNormalized(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized) { 281 RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId); 282 return calculateHashNormalized(thePartitionSettings, requestPartitionId, theModelConfig, theResourceType, theParamName, theValueNormalized); 283 } 284 285 public static long calculateHashNormalized(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized) { 286 /* 287 * If we're not allowing contained searches, we'll add the first 288 * bit of the normalized value to the hash. This helps to 289 * make the hash even more unique, which will be good for 290 * performance. 291 */ 292 int hashPrefixLength = HASH_PREFIX_LENGTH; 293 if (theModelConfig.isAllowContainsSearches()) { 294 hashPrefixLength = 0; 295 } 296 297 String value = StringUtil.left(theValueNormalized, hashPrefixLength); 298 return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value); 299 } 300 301}