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.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", columnList = "HASH_NORM_PREFIX,SP_VALUE_NORMALIZED"),
063        @Index(name = "IDX_SP_STRING_HASH_EXCT", columnList = "HASH_EXACT"),
064
065        @Index(name = "IDX_SP_STRING_UPDATED", columnList = "SP_UPDATED"),
066        @Index(name = "IDX_SP_STRING_RESID", columnList = "RES_ID")
067})
068public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchParam {
069
070        /*
071         * Note that MYSQL chokes on unique indexes for lengths > 255 so be careful here
072         */
073        public static final int MAX_LENGTH = 200;
074        public static final int HASH_PREFIX_LENGTH = 1;
075        private static final long serialVersionUID = 1L;
076        @Id
077        @SequenceGenerator(name = "SEQ_SPIDX_STRING", sequenceName = "SEQ_SPIDX_STRING")
078        @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_STRING")
079        @Column(name = "SP_ID")
080        private Long myId;
081
082        @ManyToOne(optional = false)
083        @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", insertable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_SPIDXSTR_RESOURCE"))
084        private ResourceTable myResourceTable;
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                return b.isEquals();
175        }
176
177        private Long getHashIdentity() {
178                return myHashIdentity;
179        }
180
181        public void setHashIdentity(Long theHashIdentity) {
182                myHashIdentity = theHashIdentity;
183        }
184
185        public Long getHashExact() {
186                return myHashExact;
187        }
188
189        public void setHashExact(Long theHashExact) {
190                myHashExact = theHashExact;
191        }
192
193        public Long getHashNormalizedPrefix() {
194                return myHashNormalizedPrefix;
195        }
196
197        public void setHashNormalizedPrefix(Long theHashNormalizedPrefix) {
198                myHashNormalizedPrefix = theHashNormalizedPrefix;
199        }
200
201        @Override
202        public Long getId() {
203                return myId;
204        }
205
206        @Override
207        public void setId(Long theId) {
208                myId = theId;
209        }
210
211
212        public String getValueExact() {
213                return myValueExact;
214        }
215
216        public ResourceIndexedSearchParamString setValueExact(String theValueExact) {
217                if (defaultString(theValueExact).length() > MAX_LENGTH) {
218                        throw new IllegalArgumentException(Msg.code(1529) + "Value is too long: " + theValueExact.length());
219                }
220                myValueExact = theValueExact;
221                return this;
222        }
223
224        public String getValueNormalized() {
225                return myValueNormalized;
226        }
227
228        public ResourceIndexedSearchParamString setValueNormalized(String theValueNormalized) {
229                if (defaultString(theValueNormalized).length() > MAX_LENGTH) {
230                        throw new IllegalArgumentException(Msg.code(1530) + "Value is too long: " + theValueNormalized.length());
231                }
232                myValueNormalized = theValueNormalized;
233                return this;
234        }
235
236        @Override
237        public int hashCode() {
238                HashCodeBuilder b = new HashCodeBuilder();
239                b.append(getResourceType());
240                b.append(getParamName());
241                b.append(getValueExact());
242                return b.toHashCode();
243        }
244
245        @Override
246        public IQueryParameterType toQueryParameterType() {
247                return new StringParam(getValueExact());
248        }
249
250        @Override
251        public String toString() {
252                ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
253                b.append("paramName", getParamName());
254                b.append("resourceId", getResourcePid());
255                b.append("hashNormalizedPrefix", getHashNormalizedPrefix());
256                b.append("valueNormalized", getValueNormalized());
257                b.append("partitionId", getPartitionId());
258                return b.build();
259        }
260
261        @Override
262        public boolean matches(IQueryParameterType theParam) {
263                if (!(theParam instanceof StringParam)) {
264                        return false;
265                }
266                StringParam string = (StringParam) theParam;
267                String normalizedString = StringUtil.normalizeStringForSearchIndexing(defaultString(string.getValue()));
268                return defaultString(getValueNormalized()).startsWith(normalizedString);
269        }
270
271        public static long calculateHashExact(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValueExact) {
272                RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
273                return calculateHashExact(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theValueExact);
274        }
275
276        public static long calculateHashExact(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValueExact) {
277                return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theValueExact);
278        }
279
280        public static long calculateHashNormalized(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized) {
281                RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
282                return calculateHashNormalized(thePartitionSettings, requestPartitionId, theModelConfig, theResourceType, theParamName, theValueNormalized);
283        }
284
285        public static long calculateHashNormalized(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized) {
286                /*
287                 * If we're not allowing contained searches, we'll add the first
288                 * bit of the normalized value to the hash. This helps to
289                 * make the hash even more unique, which will be good for
290                 * performance.
291                 */
292                int hashPrefixLength = HASH_PREFIX_LENGTH;
293                if (theModelConfig.isAllowContainsSearches()) {
294                        hashPrefixLength = 0;
295                }
296
297                String value = StringUtil.left(theValueNormalized, hashPrefixLength);
298                return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value);
299        }
300
301}