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.i18n.Msg; 023import ca.uhn.fhir.interceptor.model.RequestPartitionId; 024import ca.uhn.fhir.jpa.model.config.PartitionSettings; 025import ca.uhn.fhir.model.api.IQueryParameterType; 026import ca.uhn.fhir.rest.api.Constants; 027import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 028import ca.uhn.fhir.util.UrlUtil; 029import com.google.common.base.Charsets; 030import com.google.common.hash.HashCode; 031import com.google.common.hash.HashFunction; 032import com.google.common.hash.Hasher; 033import com.google.common.hash.Hashing; 034import jakarta.persistence.Column; 035import jakarta.persistence.MappedSuperclass; 036import jakarta.persistence.Temporal; 037import jakarta.persistence.TemporalType; 038import jakarta.persistence.Transient; 039import org.apache.commons.lang3.StringUtils; 040import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; 041import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField; 042 043import java.util.Date; 044import java.util.List; 045 046@MappedSuperclass 047public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex { 048 static final int MAX_SP_NAME = 100; 049 /** 050 * Don't change this without careful consideration. You will break existing hashes! 051 */ 052 private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128(0); 053 054 /** 055 * Don't make this public 'cause nobody better be able to modify it! 056 */ 057 private static final byte[] DELIMITER_BYTES = "|".getBytes(Charsets.UTF_8); 058 059 private static final long serialVersionUID = 1L; 060 061 @GenericField 062 @Column(name = "SP_MISSING", nullable = false) 063 private boolean myMissing = false; 064 065 @FullTextField 066 @Column(name = "SP_NAME", length = MAX_SP_NAME, nullable = false) 067 private String myParamName; 068 069 @Column(name = "RES_ID", insertable = false, updatable = false, nullable = false) 070 private Long myResourcePid; 071 072 @FullTextField 073 @Column(name = "RES_TYPE", updatable = false, nullable = false, length = Constants.MAX_RESOURCE_NAME_LENGTH) 074 private String myResourceType; 075 076 @GenericField 077 @Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3 078 @Temporal(TemporalType.TIMESTAMP) 079 private Date myUpdated; 080 081 @Transient 082 private transient PartitionSettings myPartitionSettings; 083 084 @Transient 085 private transient StorageSettings myStorageSettings; 086 087 @Override 088 public abstract Long getId(); 089 090 public String getParamName() { 091 return myParamName; 092 } 093 094 public void setParamName(String theName) { 095 if (!StringUtils.equals(myParamName, theName)) { 096 myParamName = theName; 097 clearHashes(); 098 } 099 } 100 101 // MB pushed these down to the individual SP classes so we could name the FK in the join annotation 102 /** 103 * Get the Resource this SP indexes 104 */ 105 public abstract ResourceTable getResource(); 106 107 public abstract BaseResourceIndexedSearchParam setResource(ResourceTable theResource); 108 109 @Override 110 public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) { 111 BaseResourceIndexedSearchParam source = (BaseResourceIndexedSearchParam) theSource; 112 myMissing = source.myMissing; 113 myParamName = source.myParamName; 114 myUpdated = source.myUpdated; 115 myStorageSettings = source.myStorageSettings; 116 myPartitionSettings = source.myPartitionSettings; 117 setPartitionId(source.getPartitionId()); 118 } 119 120 public Long getResourcePid() { 121 return myResourcePid; 122 } 123 124 public String getResourceType() { 125 return myResourceType; 126 } 127 128 public void setResourceType(String theResourceType) { 129 myResourceType = theResourceType; 130 } 131 132 public Date getUpdated() { 133 return myUpdated; 134 } 135 136 public void setUpdated(Date theUpdated) { 137 myUpdated = theUpdated; 138 } 139 140 public boolean isMissing() { 141 return myMissing; 142 } 143 144 public BaseResourceIndexedSearchParam setMissing(boolean theMissing) { 145 myMissing = theMissing; 146 return this; 147 } 148 149 public abstract IQueryParameterType toQueryParameterType(); 150 151 public boolean matches(IQueryParameterType theParam) { 152 throw new UnsupportedOperationException(Msg.code(1526) + "No parameter matcher for " + theParam); 153 } 154 155 public PartitionSettings getPartitionSettings() { 156 return myPartitionSettings; 157 } 158 159 public BaseResourceIndexedSearchParam setPartitionSettings(PartitionSettings thePartitionSettings) { 160 myPartitionSettings = thePartitionSettings; 161 return this; 162 } 163 164 public StorageSettings getStorageSettings() { 165 return myStorageSettings; 166 } 167 168 public BaseResourceIndexedSearchParam setStorageSettings(StorageSettings theStorageSettings) { 169 myStorageSettings = theStorageSettings; 170 return this; 171 } 172 173 public static long calculateHashIdentity( 174 PartitionSettings thePartitionSettings, 175 PartitionablePartitionId theRequestPartitionId, 176 String theResourceType, 177 String theParamName) { 178 RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId); 179 return calculateHashIdentity(thePartitionSettings, requestPartitionId, theResourceType, theParamName); 180 } 181 182 public static long calculateHashIdentity( 183 PartitionSettings thePartitionSettings, 184 RequestPartitionId theRequestPartitionId, 185 String theResourceType, 186 String theParamName) { 187 return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName); 188 } 189 190 public static long calculateHashIdentity( 191 PartitionSettings thePartitionSettings, 192 RequestPartitionId theRequestPartitionId, 193 String theResourceType, 194 String theParamName, 195 List<String> theAdditionalValues) { 196 String[] values = new String[theAdditionalValues.size() + 2]; 197 values[0] = theResourceType; 198 values[1] = theParamName; 199 for (int i = 0; i < theAdditionalValues.size(); i++) { 200 values[i + 2] = theAdditionalValues.get(i); 201 } 202 203 return hash(thePartitionSettings, theRequestPartitionId, values); 204 } 205 206 /** 207 * Applies a fast and consistent hashing algorithm to a set of strings 208 */ 209 static long hash( 210 PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String... theValues) { 211 Hasher hasher = HASH_FUNCTION.newHasher(); 212 213 if (thePartitionSettings.isPartitioningEnabled() 214 && thePartitionSettings.isIncludePartitionInSearchHashes() 215 && theRequestPartitionId != null) { 216 if (theRequestPartitionId.getPartitionIds().size() > 1) { 217 throw new InternalErrorException(Msg.code(1527) 218 + "Can not search multiple partitions when partitions are included in search hashes"); 219 } 220 Integer partitionId = theRequestPartitionId.getFirstPartitionIdOrNull(); 221 if (partitionId != null) { 222 hasher.putInt(partitionId); 223 } 224 } 225 226 for (String next : theValues) { 227 if (next == null) { 228 hasher.putByte((byte) 0); 229 } else { 230 next = UrlUtil.escapeUrlParam(next); 231 byte[] bytes = next.getBytes(Charsets.UTF_8); 232 hasher.putBytes(bytes); 233 } 234 hasher.putBytes(DELIMITER_BYTES); 235 } 236 237 HashCode hashCode = hasher.hash(); 238 long retVal = hashCode.asLong(); 239 return retVal; 240 } 241}