001package ca.uhn.fhir.jpa.model.entity;
002
003/*
004 * #%L
005 * HAPI FHIR JPA Model
006 * %%
007 * Copyright (C) 2014 - 2023 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.param.StringParam;
028import ca.uhn.fhir.util.StringUtil;
029import org.apache.commons.lang3.builder.EqualsBuilder;
030import org.apache.commons.lang3.builder.HashCodeBuilder;
031import org.apache.commons.lang3.builder.ToStringBuilder;
032import org.apache.commons.lang3.builder.ToStringStyle;
033
034import javax.persistence.Column;
035import javax.persistence.Embeddable;
036import javax.persistence.Entity;
037import javax.persistence.ForeignKey;
038import javax.persistence.GeneratedValue;
039import javax.persistence.GenerationType;
040import javax.persistence.Id;
041import javax.persistence.Index;
042import javax.persistence.JoinColumn;
043import javax.persistence.ManyToOne;
044import javax.persistence.SequenceGenerator;
045import javax.persistence.Table;
046
047import static org.apache.commons.lang3.StringUtils.defaultString;
048
049//@formatter:off
050@Embeddable
051@Entity
052@Table(name = "HFJ_SPIDX_STRING", indexes = {
053        /*
054         * Note: We previously had indexes with the following names,
055         * do not reuse these names:
056         * IDX_SP_STRING
057         */
058
059        // This is used for sorting, and for :contains queries currently
060        @Index(name = "IDX_SP_STRING_HASH_IDENT", columnList = "HASH_IDENTITY"),
061
062        @Index(name = "IDX_SP_STRING_HASH_NRM_V2", columnList = "HASH_NORM_PREFIX,SP_VALUE_NORMALIZED,RES_ID,PARTITION_ID"),
063        @Index(name = "IDX_SP_STRING_HASH_EXCT_V2", columnList = "HASH_EXACT,RES_ID,PARTITION_ID"),
064
065        @Index(name = "IDX_SP_STRING_RESID", columnList = "RES_ID")
066})
067public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchParam {
068
069        /*
070         * Note that MYSQL chokes on unique indexes for lengths > 255 so be careful here
071         */
072        public static final int MAX_LENGTH = 200;
073        public static final int HASH_PREFIX_LENGTH = 1;
074        private static final long serialVersionUID = 1L;
075        @Id
076        @SequenceGenerator(name = "SEQ_SPIDX_STRING", sequenceName = "SEQ_SPIDX_STRING")
077        @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_STRING")
078        @Column(name = "SP_ID")
079        private Long myId;
080
081        @ManyToOne(optional = false)
082        @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false,
083                foreignKey = @ForeignKey(name = "FK_SPIDXSTR_RESOURCE"))
084        private ResourceTable myResource;
085
086        @Column(name = "SP_VALUE_EXACT", length = MAX_LENGTH, nullable = true)
087        private String myValueExact;
088
089        @Column(name = "SP_VALUE_NORMALIZED", length = MAX_LENGTH, nullable = true)
090        private String myValueNormalized;
091        /**
092         * @since 3.4.0 - At some point this should be made not-null
093         */
094        @Column(name = "HASH_NORM_PREFIX", nullable = true)
095        private Long myHashNormalizedPrefix;
096        /**
097         * @since 3.6.0 - At some point this should be made not-null
098         */
099        @Column(name = "HASH_IDENTITY", nullable = true)
100        private Long myHashIdentity;
101        /**
102         * @since 3.4.0 - At some point this should be made not-null
103         */
104        @Column(name = "HASH_EXACT", nullable = true)
105        private Long myHashExact;
106
107        public ResourceIndexedSearchParamString() {
108                super();
109        }
110
111        public ResourceIndexedSearchParamString(PartitionSettings thePartitionSettings, ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized, String theValueExact) {
112                setPartitionSettings(thePartitionSettings);
113                setModelConfig(theModelConfig);
114                setResourceType(theResourceType);
115                setParamName(theParamName);
116                setValueNormalized(theValueNormalized);
117                setValueExact(theValueExact);
118                calculateHashes();
119        }
120
121        @Override
122        public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
123                super.copyMutableValuesFrom(theSource);
124                ResourceIndexedSearchParamString source = (ResourceIndexedSearchParamString) theSource;
125                myValueExact = source.myValueExact;
126                myValueNormalized = source.myValueNormalized;
127                myHashExact = source.myHashExact;
128                myHashIdentity = source.myHashIdentity;
129                myHashNormalizedPrefix = source.myHashNormalizedPrefix;
130        }
131
132        @Override
133        public void clearHashes() {
134                myHashIdentity = null;
135                myHashNormalizedPrefix = null;
136                myHashExact = null;
137        }
138
139
140        @Override
141        public void calculateHashes() {
142                if (myHashIdentity != null || myHashExact != null || myHashNormalizedPrefix != null) {
143                        return;
144                }
145
146                String resourceType = getResourceType();
147                String paramName = getParamName();
148                String valueNormalized = getValueNormalized();
149                String valueExact = getValueExact();
150                setHashNormalizedPrefix(calculateHashNormalized(getPartitionSettings(), getPartitionId(), getModelConfig(), resourceType, paramName, valueNormalized));
151                setHashExact(calculateHashExact(getPartitionSettings(), getPartitionId(), resourceType, paramName, valueExact));
152                setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
153        }
154
155        @Override
156        public boolean equals(Object theObj) {
157                if (this == theObj) {
158                        return true;
159                }
160                if (theObj == null) {
161                        return false;
162                }
163                if (!(theObj instanceof ResourceIndexedSearchParamString)) {
164                        return false;
165                }
166                ResourceIndexedSearchParamString obj = (ResourceIndexedSearchParamString) theObj;
167                EqualsBuilder b = new EqualsBuilder();
168                b.append(getResourceType(), obj.getResourceType());
169                b.append(getParamName(), obj.getParamName());
170                b.append(getValueExact(), obj.getValueExact());
171                b.append(getHashIdentity(), obj.getHashIdentity());
172                b.append(getHashExact(), obj.getHashExact());
173                b.append(getHashNormalizedPrefix(), obj.getHashNormalizedPrefix());
174                b.append(getValueNormalized(), obj.getValueNormalized());
175                return b.isEquals();
176        }
177
178        private Long getHashIdentity() {
179                return myHashIdentity;
180        }
181
182        public void setHashIdentity(Long theHashIdentity) {
183                myHashIdentity = theHashIdentity;
184        }
185
186        public Long getHashExact() {
187                return myHashExact;
188        }
189
190        public void setHashExact(Long theHashExact) {
191                myHashExact = theHashExact;
192        }
193
194        public Long getHashNormalizedPrefix() {
195                return myHashNormalizedPrefix;
196        }
197
198        public void setHashNormalizedPrefix(Long theHashNormalizedPrefix) {
199                myHashNormalizedPrefix = theHashNormalizedPrefix;
200        }
201
202        @Override
203        public Long getId() {
204                return myId;
205        }
206
207        @Override
208        public void setId(Long theId) {
209                myId = theId;
210        }
211
212
213        public String getValueExact() {
214                return myValueExact;
215        }
216
217        public ResourceIndexedSearchParamString setValueExact(String theValueExact) {
218                if (defaultString(theValueExact).length() > MAX_LENGTH) {
219                        throw new IllegalArgumentException(Msg.code(1529) + "Value is too long: " + theValueExact.length());
220                }
221                myValueExact = theValueExact;
222                return this;
223        }
224
225        public String getValueNormalized() {
226                return myValueNormalized;
227        }
228
229        public ResourceIndexedSearchParamString setValueNormalized(String theValueNormalized) {
230                if (defaultString(theValueNormalized).length() > MAX_LENGTH) {
231                        throw new IllegalArgumentException(Msg.code(1530) + "Value is too long: " + theValueNormalized.length());
232                }
233                myValueNormalized = theValueNormalized;
234                return this;
235        }
236
237        @Override
238        public int hashCode() {
239                HashCodeBuilder b = new HashCodeBuilder();
240                b.append(getResourceType());
241                b.append(getParamName());
242                b.append(getValueExact());
243                b.append(getHashIdentity());
244                b.append(getHashExact());
245                b.append(getHashNormalizedPrefix());
246                b.append(getValueNormalized());
247                return b.toHashCode();
248        }
249
250        @Override
251        public IQueryParameterType toQueryParameterType() {
252                return new StringParam(getValueExact());
253        }
254
255        @Override
256        public String toString() {
257                ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
258                b.append("paramName", getParamName());
259                b.append("resourceId", getResourcePid());
260                b.append("hashNormalizedPrefix", getHashNormalizedPrefix());
261                b.append("valueNormalized", getValueNormalized());
262                b.append("partitionId", getPartitionId());
263                return b.build();
264        }
265
266        @Override
267        public boolean matches(IQueryParameterType theParam) {
268                if (!(theParam instanceof StringParam)) {
269                        return false;
270                }
271                StringParam string = (StringParam) theParam;
272                String normalizedString = StringUtil.normalizeStringForSearchIndexing(defaultString(string.getValue()));
273                return defaultString(getValueNormalized()).startsWith(normalizedString);
274        }
275
276        public static long calculateHashExact(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValueExact) {
277                RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
278                return calculateHashExact(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theValueExact);
279        }
280
281        public static long calculateHashExact(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValueExact) {
282                return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theValueExact);
283        }
284
285        public static long calculateHashNormalized(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized) {
286                RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
287                return calculateHashNormalized(thePartitionSettings, requestPartitionId, theModelConfig, theResourceType, theParamName, theValueNormalized);
288        }
289
290        public static long calculateHashNormalized(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized) {
291                /*
292                 * If we're not allowing contained searches, we'll add the first
293                 * bit of the normalized value to the hash. This helps to
294                 * make the hash even more unique, which will be good for
295                 * performance.
296                 */
297                int hashPrefixLength = HASH_PREFIX_LENGTH;
298                if (theModelConfig.isAllowContainsSearches()) {
299                        hashPrefixLength = 0;
300                }
301
302                String value = StringUtil.left(theValueNormalized, hashPrefixLength);
303                return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value);
304        }
305
306        @Override
307        public ResourceTable getResource() {
308                return myResource;
309        }
310
311        @Override
312        public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
313                myResource = theResource;
314                setResourceType(theResource.getResourceType());
315                return this;
316        }
317}