001/* 002 * #%L 003 * HAPI FHIR JPA Model 004 * %% 005 * Copyright (C) 2014 - 2025 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.param.UriParam; 027import jakarta.persistence.Column; 028import jakarta.persistence.Entity; 029import jakarta.persistence.EntityListeners; 030import jakarta.persistence.FetchType; 031import jakarta.persistence.ForeignKey; 032import jakarta.persistence.GeneratedValue; 033import jakarta.persistence.GenerationType; 034import jakarta.persistence.Id; 035import jakarta.persistence.IdClass; 036import jakarta.persistence.Index; 037import jakarta.persistence.JoinColumn; 038import jakarta.persistence.JoinColumns; 039import jakarta.persistence.ManyToOne; 040import jakarta.persistence.Table; 041import org.apache.commons.lang3.StringUtils; 042import org.apache.commons.lang3.builder.EqualsBuilder; 043import org.apache.commons.lang3.builder.HashCodeBuilder; 044import org.apache.commons.lang3.builder.ToStringBuilder; 045import org.hibernate.annotations.GenericGenerator; 046import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; 047 048import static ca.uhn.fhir.jpa.model.util.SearchParamHash.hashSearchParam; 049import static org.apache.commons.lang3.StringUtils.defaultString; 050 051@EntityListeners(IndexStorageOptimizationListener.class) 052@Entity 053@Table( 054 name = "HFJ_SPIDX_URI", 055 indexes = { 056 // for queries 057 @Index(name = "IDX_SP_URI_HASH_URI_V2", columnList = "HASH_URI,RES_ID,PARTITION_ID"), 058 // for sorting 059 @Index(name = "IDX_SP_URI_HASH_IDENTITY_V2", columnList = "HASH_IDENTITY,SP_URI,RES_ID,PARTITION_ID"), 060 // for index create/delete 061 @Index(name = "IDX_SP_URI_COORDS", columnList = "RES_ID") 062 }) 063@IdClass(IdAndPartitionId.class) 064public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchParam { 065 066 /* 067 * Be careful when modifying this value 068 * MySQL chokes on indexes with combined column length greater than 3052 bytes (768 chars) 069 * https://dev.mysql.com/doc/refman/8.0/en/innodb-limits.html 070 */ 071 public static final int MAX_LENGTH = 500; 072 073 private static final long serialVersionUID = 1L; 074 075 @Column(name = "SP_URI", nullable = true, length = MAX_LENGTH) 076 @FullTextField 077 public String myUri; 078 079 @Id 080 @GenericGenerator(name = "SEQ_SPIDX_URI", type = ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator.class) 081 @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_URI") 082 @Column(name = "SP_ID") 083 private Long myId; 084 085 /** 086 * @since 3.4.0 - At some point this should be made not-null 087 */ 088 @Column(name = "HASH_URI", nullable = true) 089 private Long myHashUri; 090 091 @ManyToOne( 092 optional = false, 093 fetch = FetchType.LAZY, 094 cascade = {}) 095 @JoinColumns( 096 value = { 097 @JoinColumn( 098 name = "RES_ID", 099 referencedColumnName = "RES_ID", 100 insertable = false, 101 updatable = false, 102 nullable = false), 103 @JoinColumn( 104 name = "PARTITION_ID", 105 referencedColumnName = "PARTITION_ID", 106 insertable = false, 107 updatable = false, 108 nullable = false) 109 }, 110 foreignKey = @ForeignKey(name = "FKGXSREUTYMMFJUWDSWV3Y887DO")) 111 private ResourceTable myResource; 112 113 @Column(name = "RES_ID", nullable = false) 114 private Long myResourceId; 115 116 /** 117 * Constructor 118 */ 119 public ResourceIndexedSearchParamUri() { 120 super(); 121 } 122 123 /** 124 * Constructor 125 */ 126 public ResourceIndexedSearchParamUri( 127 PartitionSettings thePartitionSettings, String theResourceType, String theParamName, String theUri) { 128 setPartitionSettings(thePartitionSettings); 129 setResourceType(theResourceType); 130 setParamName(theParamName); 131 setUri(theUri); 132 calculateHashes(); 133 } 134 135 @Override 136 public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) { 137 super.copyMutableValuesFrom(theSource); 138 ResourceIndexedSearchParamUri source = (ResourceIndexedSearchParamUri) theSource; 139 myUri = source.myUri; 140 myHashUri = source.myHashUri; 141 myHashIdentity = source.myHashIdentity; 142 } 143 144 @Override 145 public void setResourceId(Long theResourceId) { 146 myResourceId = theResourceId; 147 } 148 149 @Override 150 public void clearHashes() { 151 myHashIdentity = null; 152 myHashUri = null; 153 } 154 155 @Override 156 public void calculateHashes() { 157 if (myHashIdentity != null || myHashUri != null) { 158 return; 159 } 160 161 String resourceType = getResourceType(); 162 String paramName = getParamName(); 163 String uri = getUri(); 164 setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); 165 setHashUri(calculateHashUri(getPartitionSettings(), getPartitionId(), resourceType, paramName, uri)); 166 } 167 168 @Override 169 public boolean equals(Object theObj) { 170 if (this == theObj) { 171 return true; 172 } 173 if (theObj == null) { 174 return false; 175 } 176 if (!(theObj instanceof ResourceIndexedSearchParamUri)) { 177 return false; 178 } 179 ResourceIndexedSearchParamUri obj = (ResourceIndexedSearchParamUri) theObj; 180 EqualsBuilder b = new EqualsBuilder(); 181 b.append(getUri(), obj.getUri()); 182 b.append(getHashUri(), obj.getHashUri()); 183 b.append(getHashIdentity(), obj.getHashIdentity()); 184 b.append(isMissing(), obj.isMissing()); 185 return b.isEquals(); 186 } 187 188 public Long getHashUri() { 189 return myHashUri; 190 } 191 192 public void setHashUri(Long theHashUri) { 193 myHashUri = theHashUri; 194 } 195 196 @Override 197 public Long getId() { 198 return myId; 199 } 200 201 @Override 202 public void setId(Long theId) { 203 myId = theId; 204 } 205 206 public String getUri() { 207 return myUri; 208 } 209 210 public ResourceIndexedSearchParamUri setUri(String theUri) { 211 myUri = StringUtils.defaultIfBlank(theUri, null); 212 return this; 213 } 214 215 @Override 216 public int hashCode() { 217 HashCodeBuilder b = new HashCodeBuilder(); 218 b.append(getUri()); 219 b.append(getHashUri()); 220 b.append(getHashIdentity()); 221 b.append(isMissing()); 222 return b.toHashCode(); 223 } 224 225 @Override 226 public IQueryParameterType toQueryParameterType() { 227 return new UriParam(getUri()); 228 } 229 230 @Override 231 public String toString() { 232 ToStringBuilder b = new ToStringBuilder(this); 233 b.append("id", getId()); 234 b.append("resourceId", getResourcePid()); 235 b.append("paramName", getParamName()); 236 b.append("uri", myUri); 237 b.append("hashUri", myHashUri); 238 b.append("hashIdentity", myHashIdentity); 239 return b.toString(); 240 } 241 242 @Override 243 public boolean matches(IQueryParameterType theParam) { 244 if (!(theParam instanceof UriParam)) { 245 return false; 246 } 247 UriParam uri = (UriParam) theParam; 248 return defaultString(getUri()).equalsIgnoreCase(uri.getValueNotNull()); 249 } 250 251 public static long calculateHashUri( 252 PartitionSettings thePartitionSettings, 253 PartitionablePartitionId theRequestPartitionId, 254 String theResourceType, 255 String theParamName, 256 String theUri) { 257 RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId); 258 return calculateHashUri(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theUri); 259 } 260 261 public static long calculateHashUri( 262 PartitionSettings thePartitionSettings, 263 RequestPartitionId theRequestPartitionId, 264 String theResourceType, 265 String theParamName, 266 String theUri) { 267 return hashSearchParam(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theUri); 268 } 269 270 @Override 271 public ResourceTable getResource() { 272 return myResource; 273 } 274 275 @Override 276 public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) { 277 setResourceType(theResource.getResourceType()); 278 return this; 279 } 280}