001package ca.uhn.fhir.jpa.model.entity; 002 003/* 004 * #%L 005 * HAPI FHIR JPA Model 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.interceptor.model.RequestPartitionId; 025import ca.uhn.fhir.jpa.model.config.PartitionSettings; 026import ca.uhn.fhir.model.api.IQueryParameterType; 027import ca.uhn.fhir.rest.api.Constants; 028import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 029import ca.uhn.fhir.util.UrlUtil; 030import com.google.common.base.Charsets; 031import com.google.common.hash.HashCode; 032import com.google.common.hash.HashFunction; 033import com.google.common.hash.Hasher; 034import com.google.common.hash.Hashing; 035import org.apache.commons.lang3.StringUtils; 036import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; 037import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField; 038 039import javax.persistence.Column; 040import javax.persistence.FetchType; 041import javax.persistence.JoinColumn; 042import javax.persistence.ManyToOne; 043import javax.persistence.MappedSuperclass; 044import javax.persistence.Temporal; 045import javax.persistence.TemporalType; 046import javax.persistence.Transient; 047import java.util.Date; 048import java.util.List; 049 050@MappedSuperclass 051public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex { 052 static final int MAX_SP_NAME = 100; 053 /** 054 * Don't change this without careful consideration. You will break existing hashes! 055 */ 056 private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128(0); 057 058 /** 059 * Don't make this public 'cause nobody better be able to modify it! 060 */ 061 private static final byte[] DELIMITER_BYTES = "|".getBytes(Charsets.UTF_8); 062 private static final long serialVersionUID = 1L; 063 064 @GenericField 065 @Column(name = "SP_MISSING", nullable = false) 066 private boolean myMissing = false; 067 068 @FullTextField 069 @Column(name = "SP_NAME", length = MAX_SP_NAME, nullable = false) 070 private String myParamName; 071 072 @ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {}) 073 @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false) 074 private ResourceTable myResource; 075 076 @Column(name = "RES_ID", insertable = false, updatable = false, nullable = false) 077 private Long myResourcePid; 078 079 @FullTextField 080 @Column(name = "RES_TYPE", updatable = false, nullable = false, length = Constants.MAX_RESOURCE_NAME_LENGTH) 081 private String myResourceType; 082 083 @GenericField 084 @Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3 085 @Temporal(TemporalType.TIMESTAMP) 086 private Date myUpdated; 087 088 @Transient 089 private transient PartitionSettings myPartitionSettings; 090 091 @Transient 092 private transient ModelConfig myModelConfig; 093 094 @Override 095 public abstract Long getId(); 096 097 public String getParamName() { 098 return myParamName; 099 } 100 101 public void setParamName(String theName) { 102 if (!StringUtils.equals(myParamName, theName)) { 103 myParamName = theName; 104 clearHashes(); 105 } 106 } 107 108 public ResourceTable getResource() { 109 return myResource; 110 } 111 112 public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) { 113 myResource = theResource; 114 myResourceType = theResource.getResourceType(); 115 return this; 116 } 117 118 @Override 119 public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) { 120 BaseResourceIndexedSearchParam source = (BaseResourceIndexedSearchParam) theSource; 121 myMissing = source.myMissing; 122 myParamName = source.myParamName; 123 myUpdated = source.myUpdated; 124 myModelConfig = source.myModelConfig; 125 myPartitionSettings = source.myPartitionSettings; 126 setPartitionId(source.getPartitionId()); 127 } 128 129 public Long getResourcePid() { 130 return myResourcePid; 131 } 132 133 public String getResourceType() { 134 return myResourceType; 135 } 136 137 public void setResourceType(String theResourceType) { 138 myResourceType = theResourceType; 139 } 140 141 public Date getUpdated() { 142 return myUpdated; 143 } 144 145 public void setUpdated(Date theUpdated) { 146 myUpdated = theUpdated; 147 } 148 149 public boolean isMissing() { 150 return myMissing; 151 } 152 153 public BaseResourceIndexedSearchParam setMissing(boolean theMissing) { 154 myMissing = theMissing; 155 return this; 156 } 157 158 public abstract IQueryParameterType toQueryParameterType(); 159 160 public boolean matches(IQueryParameterType theParam) { 161 throw new UnsupportedOperationException(Msg.code(1526) + "No parameter matcher for " + theParam); 162 } 163 164 public PartitionSettings getPartitionSettings() { 165 return myPartitionSettings; 166 } 167 168 public BaseResourceIndexedSearchParam setPartitionSettings(PartitionSettings thePartitionSettings) { 169 myPartitionSettings = thePartitionSettings; 170 return this; 171 } 172 173 public ModelConfig getModelConfig() { 174 return myModelConfig; 175 } 176 177 public BaseResourceIndexedSearchParam setModelConfig(ModelConfig theModelConfig) { 178 myModelConfig = theModelConfig; 179 return this; 180 } 181 182 public static long calculateHashIdentity(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName) { 183 RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId); 184 return calculateHashIdentity(thePartitionSettings, requestPartitionId, theResourceType, theParamName); 185 } 186 187 public static long calculateHashIdentity(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName) { 188 return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName); 189 } 190 191 public static long calculateHashIdentity(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, List<String> theAdditionalValues) { 192 String[] values = new String[theAdditionalValues.size() + 2]; 193 values[0] = theResourceType; 194 values[1] = theParamName; 195 for (int i = 0; i < theAdditionalValues.size(); i++) { 196 values[i + 2] = theAdditionalValues.get(i); 197 } 198 199 return hash(thePartitionSettings, theRequestPartitionId, values); 200 } 201 202 /** 203 * Applies a fast and consistent hashing algorithm to a set of strings 204 */ 205 static long hash(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String... theValues) { 206 Hasher hasher = HASH_FUNCTION.newHasher(); 207 208 if (thePartitionSettings.isPartitioningEnabled() && thePartitionSettings.isIncludePartitionInSearchHashes() && theRequestPartitionId != null) { 209 if (theRequestPartitionId.getPartitionIds().size() > 1) { 210 throw new InternalErrorException(Msg.code(1527) + "Can not search multiple partitions when partitions are included in search hashes"); 211 } 212 Integer partitionId = theRequestPartitionId.getFirstPartitionIdOrNull(); 213 if (partitionId != null) { 214 hasher.putInt(partitionId); 215 } 216 } 217 218 for (String next : theValues) { 219 if (next == null) { 220 hasher.putByte((byte) 0); 221 } else { 222 next = UrlUtil.escapeUrlParam(next); 223 byte[] bytes = next.getBytes(Charsets.UTF_8); 224 hasher.putBytes(bytes); 225 } 226 hasher.putBytes(DELIMITER_BYTES); 227 } 228 229 HashCode hashCode = hasher.hash(); 230 long retVal = hashCode.asLong(); 231 return retVal; 232 } 233}