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