
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.i18n.Msg; 023import ca.uhn.fhir.interceptor.model.RequestPartitionId; 024import ca.uhn.fhir.jpa.model.config.PartitionSettings; 025import ca.uhn.fhir.jpa.model.util.SearchParamHash; 026import ca.uhn.fhir.model.api.IQueryParameterType; 027import ca.uhn.fhir.rest.api.Constants; 028import jakarta.persistence.Column; 029import jakarta.persistence.MappedSuperclass; 030import jakarta.persistence.Temporal; 031import jakarta.persistence.TemporalType; 032import jakarta.persistence.Transient; 033import org.apache.commons.lang3.Strings; 034import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; 035import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField; 036 037import java.io.Serial; 038import java.util.Date; 039import java.util.List; 040 041import static org.apache.commons.lang3.StringUtils.isNotBlank; 042 043@MappedSuperclass 044public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex { 045 static final int MAX_SP_NAME = 100; 046 047 @Serial 048 private static final long serialVersionUID = 1L; 049 050 @GenericField 051 @Column(name = "SP_MISSING", nullable = false) 052 private boolean myMissing = false; 053 054 @FullTextField 055 @Column(name = "SP_NAME", length = MAX_SP_NAME) 056 private String myParamName; 057 058 /** 059 * This is just a place to stash the {@link #myParamName} value if 060 * {@link #optimizeIndexStorage()} is called. 061 */ 062 @Transient 063 private transient String myParamNameCached; 064 065 @Column(name = "RES_ID", insertable = false, updatable = false, nullable = false) 066 private Long myResourcePid; 067 068 @FullTextField 069 @Column(name = "RES_TYPE", length = Constants.MAX_RESOURCE_NAME_LENGTH) 070 private String myResourceType; 071 072 /** 073 * Composite of resourceType, paramName, and partition info if configured. 074 * Combined with the various date fields for a query. 075 * Nullable to allow optimized storage. 076 */ 077 @Column(name = "HASH_IDENTITY", nullable = true) 078 protected Long myHashIdentity; 079 080 @GenericField 081 @Column(name = "SP_UPDATED", nullable = true) 082 @Temporal(TemporalType.TIMESTAMP) 083 private Date myUpdated; 084 085 @Transient 086 private transient PartitionSettings myPartitionSettings; 087 088 @Transient 089 private transient StorageSettings myStorageSettings; 090 091 @Override 092 public abstract Long getId(); 093 094 public String getParamName() { 095 if (myParamNameCached != null) { 096 return myParamNameCached; 097 } 098 return myParamName; 099 } 100 101 public void setParamName(String theName) { 102 if (!Strings.CS.equals(myParamName, theName)) { 103 myParamName = theName; 104 clearHashes(); 105 } 106 } 107 108 /** 109 * Restore SP_NAME without clearing hashes 110 */ 111 public void restoreParamName(String theParamName) { 112 if (myParamName == null) { 113 myParamName = theParamName; 114 } 115 } 116 117 /** 118 * Set SP_NAME, RES_TYPE to null without clearing hashes 119 */ 120 public void optimizeIndexStorage() { 121 if (isNotBlank(myParamName)) { 122 myParamNameCached = myParamName; 123 myParamName = null; 124 } 125 myResourceType = null; 126 } 127 128 public boolean isIndexStorageOptimized() { 129 return myParamName == null || myResourceType == null; 130 } 131 132 // MB pushed these down to the individual SP classes so we could name the FK in the join annotation 133 /** 134 * Get the Resource this SP indexes 135 */ 136 public abstract ResourceTable getResource(); 137 138 public abstract BaseResourceIndexedSearchParam setResource(ResourceTable theResource); 139 140 @Override 141 public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) { 142 BaseResourceIndexedSearchParam source = (BaseResourceIndexedSearchParam) theSource; 143 myMissing = source.myMissing; 144 myParamName = source.myParamName; 145 myResourceType = source.myResourceType; 146 myUpdated = source.myUpdated; 147 myStorageSettings = source.myStorageSettings; 148 myPartitionSettings = source.myPartitionSettings; 149 setPartitionId(source.getPartitionId()); 150 } 151 152 public Long getResourcePid() { 153 return myResourcePid; 154 } 155 156 public String getResourceType() { 157 return myResourceType; 158 } 159 160 public void setResourceType(String theResourceType) { 161 myResourceType = theResourceType; 162 } 163 164 public void setHashIdentity(Long theHashIdentity) { 165 myHashIdentity = theHashIdentity; 166 } 167 168 public Long getHashIdentity() { 169 return myHashIdentity; 170 } 171 172 public Date getUpdated() { 173 return myUpdated; 174 } 175 176 public void setUpdated(Date theUpdated) { 177 myUpdated = theUpdated; 178 } 179 180 public boolean isMissing() { 181 return myMissing; 182 } 183 184 public BaseResourceIndexedSearchParam setMissing(boolean theMissing) { 185 myMissing = theMissing; 186 return this; 187 } 188 189 public abstract IQueryParameterType toQueryParameterType(); 190 191 public boolean matches(IQueryParameterType theParam) { 192 throw new UnsupportedOperationException(Msg.code(1526) + "No parameter matcher for " + theParam); 193 } 194 195 public PartitionSettings getPartitionSettings() { 196 return myPartitionSettings; 197 } 198 199 public BaseResourceIndexedSearchParam setPartitionSettings(PartitionSettings thePartitionSettings) { 200 myPartitionSettings = thePartitionSettings; 201 return this; 202 } 203 204 public StorageSettings getStorageSettings() { 205 return myStorageSettings; 206 } 207 208 public BaseResourceIndexedSearchParam setStorageSettings(StorageSettings theStorageSettings) { 209 myStorageSettings = theStorageSettings; 210 return this; 211 } 212 213 public static long calculateHashIdentity( 214 PartitionSettings thePartitionSettings, 215 PartitionablePartitionId theRequestPartitionId, 216 String theResourceType, 217 String theParamName) { 218 RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId); 219 return calculateHashIdentity(thePartitionSettings, requestPartitionId, theResourceType, theParamName); 220 } 221 222 public static long calculateHashIdentity( 223 PartitionSettings thePartitionSettings, 224 RequestPartitionId theRequestPartitionId, 225 String theResourceType, 226 String theParamName) { 227 return SearchParamHash.hashSearchParam( 228 thePartitionSettings, theRequestPartitionId, theResourceType, theParamName); 229 } 230 231 public static long calculateHashIdentity( 232 PartitionSettings thePartitionSettings, 233 RequestPartitionId theRequestPartitionId, 234 String theResourceType, 235 String theParamName, 236 List<String> theAdditionalValues) { 237 String[] values = new String[theAdditionalValues.size() + 2]; 238 values[0] = theResourceType; 239 values[1] = theParamName; 240 for (int i = 0; i < theAdditionalValues.size(); i++) { 241 values[i + 2] = theAdditionalValues.get(i); 242 } 243 244 return SearchParamHash.hashSearchParam(thePartitionSettings, theRequestPartitionId, values); 245 } 246}