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