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.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.param.TokenParam;
028import org.apache.commons.lang3.StringUtils;
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;
033import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
034
035import javax.persistence.Column;
036import javax.persistence.Embeddable;
037import javax.persistence.Entity;
038import javax.persistence.GeneratedValue;
039import javax.persistence.GenerationType;
040import javax.persistence.Id;
041import javax.persistence.Index;
042import javax.persistence.SequenceGenerator;
043import javax.persistence.Table;
044
045import static org.apache.commons.lang3.StringUtils.defaultString;
046import static org.apache.commons.lang3.StringUtils.trim;
047
048@Embeddable
049@Entity
050@Table(name = "HFJ_SPIDX_TOKEN", indexes = {
051        /*
052         * Note: We previously had indexes with the following names,
053         * do not reuse these names:
054         * IDX_SP_TOKEN
055         * IDX_SP_TOKEN_UNQUAL
056         */
057
058        // TODO PERF Recommend to drop this index (added by JA - I don't actually think we even need the identity hash for this type, we could potentially drop the column too):
059        @Index(name = "IDX_SP_TOKEN_HASH", columnList = "HASH_IDENTITY"),
060        @Index(name = "IDX_SP_TOKEN_HASH_S", columnList = "HASH_SYS"),
061        @Index(name = "IDX_SP_TOKEN_HASH_SV", columnList = "HASH_SYS_AND_VALUE"),
062        // TODO PERF change this to:
063        //      @Index(name = "IDX_SP_TOKEN_HASH_V", columnList = "HASH_VALUE,RES_ID"),
064        @Index(name = "IDX_SP_TOKEN_HASH_V", columnList = "HASH_VALUE"),
065
066        @Index(name = "IDX_SP_TOKEN_UPDATED", columnList = "SP_UPDATED"),
067        @Index(name = "IDX_SP_TOKEN_RESID", columnList = "RES_ID")
068})
069public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchParam {
070
071        public static final int MAX_LENGTH = 200;
072
073        private static final long serialVersionUID = 1L;
074
075        @FullTextField
076        @Column(name = "SP_SYSTEM", nullable = true, length = MAX_LENGTH)
077        public String mySystem;
078
079        @FullTextField
080        @Column(name = "SP_VALUE", nullable = true, length = MAX_LENGTH)
081        private String myValue;
082
083        @SuppressWarnings("unused")
084        @Id
085        @SequenceGenerator(name = "SEQ_SPIDX_TOKEN", sequenceName = "SEQ_SPIDX_TOKEN")
086        @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_TOKEN")
087        @Column(name = "SP_ID")
088        private Long myId;
089        /**
090         * @since 3.4.0 - At some point this should be made not-null
091         */
092        @Column(name = "HASH_IDENTITY", nullable = true)
093        private Long myHashIdentity;
094        /**
095         * @since 3.4.0 - At some point this should be made not-null
096         */
097        @Column(name = "HASH_SYS", nullable = true)
098        private Long myHashSystem;
099        /**
100         * @since 3.4.0 - At some point this should be made not-null
101         */
102        @Column(name = "HASH_SYS_AND_VALUE", nullable = true)
103        private Long myHashSystemAndValue;
104        /**
105         * @since 3.4.0 - At some point this should be made not-null
106         */
107        @Column(name = "HASH_VALUE", nullable = true)
108        private Long myHashValue;
109
110
111        /**
112         * Constructor
113         */
114        public ResourceIndexedSearchParamToken() {
115                super();
116        }
117
118        /**
119         * Constructor
120         */
121        public ResourceIndexedSearchParamToken(PartitionSettings thePartitionSettings, String theResourceType, String theParamName, String theSystem, String theValue) {
122                super();
123                setPartitionSettings(thePartitionSettings);
124                setResourceType(theResourceType);
125                setParamName(theParamName);
126                setSystem(theSystem);
127                setValue(theValue);
128                calculateHashes();
129        }
130
131        @Override
132        public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
133                super.copyMutableValuesFrom(theSource);
134                ResourceIndexedSearchParamToken source = (ResourceIndexedSearchParamToken) theSource;
135
136                mySystem = source.mySystem;
137                myValue = source.myValue;
138                myHashSystem = source.myHashSystem;
139                myHashSystemAndValue = source.getHashSystemAndValue();
140                myHashValue = source.myHashValue;
141                myHashIdentity = source.myHashIdentity;
142        }
143
144        @Override
145        public void clearHashes() {
146                myHashIdentity = null;
147                myHashSystem = null;
148                myHashSystemAndValue = null;
149                myHashValue = null;
150        }
151
152
153        @Override
154        public void calculateHashes() {
155                if (myHashIdentity != null || myHashSystem != null || myHashValue != null || myHashSystemAndValue != null) {
156                        return;
157                }
158
159                String resourceType = getResourceType();
160                String paramName = getParamName();
161                String system = getSystem();
162                String value = getValue();
163                setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
164                setHashSystemAndValue(calculateHashSystemAndValue(getPartitionSettings(), getPartitionId(), resourceType, paramName, system, value));
165
166                // Searches using the :of-type modifier can never be partial (system-only or value-only) so don't
167                // bother saving these
168                boolean calculatePartialHashes = !StringUtils.endsWith(paramName, Constants.PARAMQUALIFIER_TOKEN_OF_TYPE);
169                if (calculatePartialHashes) {
170                        setHashSystem(calculateHashSystem(getPartitionSettings(), getPartitionId(), resourceType, paramName, system));
171                        setHashValue(calculateHashValue(getPartitionSettings(), getPartitionId(), resourceType, paramName, value));
172                }
173        }
174
175        @Override
176        public boolean equals(Object theObj) {
177                if (this == theObj) {
178                        return true;
179                }
180                if (theObj == null) {
181                        return false;
182                }
183                if (!(theObj instanceof ResourceIndexedSearchParamToken)) {
184                        return false;
185                }
186                ResourceIndexedSearchParamToken obj = (ResourceIndexedSearchParamToken) theObj;
187                EqualsBuilder b = new EqualsBuilder();
188                b.append(getHashSystem(), obj.getHashSystem());
189                b.append(getHashValue(), obj.getHashValue());
190                b.append(getHashSystemAndValue(), obj.getHashSystemAndValue());
191                return b.isEquals();
192        }
193
194        public Long getHashSystem() {
195                return myHashSystem;
196        }
197
198        private void setHashSystem(Long theHashSystem) {
199                myHashSystem = theHashSystem;
200        }
201
202        private void setHashIdentity(Long theHashIdentity) {
203                myHashIdentity = theHashIdentity;
204        }
205
206        public Long getHashSystemAndValue() {
207                return myHashSystemAndValue;
208        }
209
210        private void setHashSystemAndValue(Long theHashSystemAndValue) {
211                myHashSystemAndValue = theHashSystemAndValue;
212        }
213
214        public Long getHashValue() {
215                return myHashValue;
216        }
217
218        private void setHashValue(Long theHashValue) {
219                myHashValue = theHashValue;
220        }
221
222        @Override
223        public Long getId() {
224                return myId;
225        }
226
227        @Override
228        public void setId(Long theId) {
229                myId = theId;
230        }
231
232        public String getSystem() {
233                return mySystem;
234        }
235
236        public void setSystem(String theSystem) {
237                mySystem = StringUtils.defaultIfBlank(theSystem, null);
238                myHashSystemAndValue = null;
239        }
240
241        public String getValue() {
242                return myValue;
243        }
244
245        public ResourceIndexedSearchParamToken setValue(String theValue) {
246                myValue = StringUtils.defaultIfBlank(theValue, null);
247                myHashSystemAndValue = null;
248                return this;
249        }
250
251        @Override
252        public int hashCode() {
253                HashCodeBuilder b = new HashCodeBuilder();
254                b.append(getResourceType());
255                b.append(getHashValue());
256                b.append(getHashSystem());
257                b.append(getHashSystemAndValue());
258
259                return b.toHashCode();
260        }
261
262        @Override
263        public IQueryParameterType toQueryParameterType() {
264                return new TokenParam(getSystem(), getValue());
265        }
266
267        @Override
268        public String toString() {
269                ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
270                if (getPartitionId() != null) {
271                        b.append("partitionId", getPartitionId().getPartitionId());
272                }
273                b.append("resourceType", getResourceType());
274                b.append("paramName", getParamName());
275                if (isMissing()) {
276                        b.append("missing", true);
277                } else {
278                        b.append("system", getSystem());
279                        b.append("value", getValue());
280                }
281                b.append("hashIdentity", myHashIdentity);
282                b.append("hashSystem", myHashSystem);
283                b.append("hashValue", myHashValue);
284                b.append("hashSysAndValue", myHashSystemAndValue);
285                b.append("partition", getPartitionId());
286                return b.build();
287        }
288
289        @Override
290        public boolean matches(IQueryParameterType theParam) {
291                if (!(theParam instanceof TokenParam)) {
292                        return false;
293                }
294                TokenParam token = (TokenParam) theParam;
295                boolean retVal = false;
296                String valueString = defaultString(getValue());
297                String tokenValueString = defaultString(token.getValue());
298
299                // Only match on system if it wasn't specified
300                if (token.getSystem() == null || token.getSystem().isEmpty()) {
301                        if (valueString.equalsIgnoreCase(tokenValueString)) {
302                                retVal = true;
303                        }
304                } else if (tokenValueString == null || tokenValueString.isEmpty()) {
305                        if (token.getSystem().equalsIgnoreCase(getSystem())) {
306                                retVal = true;
307                        }
308                } else {
309                        if (token.getSystem().equalsIgnoreCase(getSystem()) &&
310                                valueString.equalsIgnoreCase(tokenValueString)) {
311                                retVal = true;
312                        }
313                }
314                return retVal;
315        }
316
317
318        public static long calculateHashSystem(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem) {
319                RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
320                return calculateHashSystem(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theSystem);
321        }
322
323        public static long calculateHashSystem(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem) {
324                return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, trim(theSystem));
325        }
326
327        public static long calculateHashSystemAndValue(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem, String theValue) {
328                RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
329                return calculateHashSystemAndValue(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theSystem, theValue);
330        }
331
332        public static long calculateHashSystemAndValue(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem, String theValue) {
333                return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, defaultString(trim(theSystem)), trim(theValue));
334        }
335
336        public static long calculateHashValue(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValue) {
337                RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
338                return calculateHashValue(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theValue);
339        }
340
341        public static long calculateHashValue(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValue) {
342                String value = trim(theValue);
343                return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value);
344        }
345
346
347}