001/*-
002 * #%L
003 * HAPI FHIR JPA Model
004 * %%
005 * Copyright (C) 2014 - 2025 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.interceptor.model.RequestPartitionId;
023import ca.uhn.fhir.jpa.model.config.PartitionSettings;
024import ca.uhn.fhir.jpa.model.util.SearchParamHash;
025import jakarta.persistence.Column;
026import jakarta.persistence.Entity;
027import jakarta.persistence.FetchType;
028import jakarta.persistence.ForeignKey;
029import jakarta.persistence.GeneratedValue;
030import jakarta.persistence.GenerationType;
031import jakarta.persistence.Id;
032import jakarta.persistence.IdClass;
033import jakarta.persistence.Index;
034import jakarta.persistence.JoinColumn;
035import jakarta.persistence.JoinColumns;
036import jakarta.persistence.ManyToOne;
037import jakarta.persistence.Table;
038import jakarta.persistence.Transient;
039import org.apache.commons.lang3.builder.CompareToBuilder;
040import org.apache.commons.lang3.builder.EqualsBuilder;
041import org.apache.commons.lang3.builder.HashCodeBuilder;
042import org.apache.commons.lang3.builder.ToStringBuilder;
043import org.hibernate.annotations.GenericGenerator;
044
045@Entity
046@Table(
047                name = ResourceIndexedComboTokenNonUnique.HFJ_IDX_CMB_TOK_NU,
048                indexes = {
049                        // TODO: The hash index was added in 7.4.0 - In 7.6.0 we should drop the string index
050                        // As of 8.6.0 the string equivalent column is nullable
051                        @Index(name = "IDX_IDXCMBTOKNU_STR", columnList = "IDX_STRING", unique = false),
052                        @Index(name = "IDX_IDXCMBTOKNU_HASHC", columnList = "HASH_COMPLETE,RES_ID,PARTITION_ID", unique = false),
053                        @Index(name = "IDX_IDXCMBTOKNU_RES", columnList = "RES_ID", unique = false)
054                })
055@IdClass(IdAndPartitionId.class)
056public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndexedCombo
057                implements Comparable<ResourceIndexedComboTokenNonUnique>, IResourceIndexComboSearchParameter {
058
059        public static final String HFJ_IDX_CMB_TOK_NU = "HFJ_IDX_CMB_TOK_NU";
060
061        @GenericGenerator(
062                        name = "SEQ_IDXCMBTOKNU_ID",
063                        type = ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator.class)
064        @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_IDXCMBTOKNU_ID")
065        @Id
066        @Column(name = "PID")
067        private Long myId;
068
069        @ManyToOne(
070                        optional = false,
071                        fetch = FetchType.LAZY,
072                        cascade = {})
073        @JoinColumns(
074                        value = {
075                                @JoinColumn(
076                                                name = "RES_ID",
077                                                referencedColumnName = "RES_ID",
078                                                insertable = false,
079                                                updatable = false,
080                                                nullable = true),
081                                @JoinColumn(
082                                                name = "PARTITION_ID",
083                                                referencedColumnName = "PARTITION_ID",
084                                                insertable = false,
085                                                updatable = false,
086                                                nullable = true)
087                        },
088                        foreignKey = @ForeignKey(name = "FK_IDXCMBTOKNU_RES_ID"))
089        private ResourceTable myResource;
090
091        @Column(name = "RES_ID", updatable = false, nullable = true)
092        private Long myResourceId;
093
094        @Column(name = "HASH_COMPLETE", nullable = false)
095        private Long myHashComplete;
096
097        @Column(name = "IDX_STRING", nullable = true, length = ResourceIndexedComboStringUnique.MAX_STRING_LENGTH)
098        private String myIndexString;
099
100        @Transient
101        private transient PartitionSettings myPartitionSettings;
102
103        /**
104         * Constructor
105         */
106        public ResourceIndexedComboTokenNonUnique() {
107                super();
108        }
109
110        public ResourceIndexedComboTokenNonUnique(
111                        PartitionSettings thePartitionSettings, ResourceTable theEntity, String theQueryString) {
112                myPartitionSettings = thePartitionSettings;
113                myResource = theEntity;
114                myIndexString = theQueryString;
115                calculateHashes();
116        }
117
118        @Override
119        public String getIndexString() {
120                return myIndexString;
121        }
122
123        public void setIndexString(String theIndexString) {
124                myIndexString = theIndexString;
125        }
126
127        @Override
128        public boolean equals(Object theO) {
129                calculateHashes();
130
131                if (this == theO) {
132                        return true;
133                }
134
135                if (theO == null || getClass() != theO.getClass()) {
136                        return false;
137                }
138
139                ResourceIndexedComboTokenNonUnique that = (ResourceIndexedComboTokenNonUnique) theO;
140
141                EqualsBuilder b = new EqualsBuilder();
142                b.append(getHashComplete(), that.getHashComplete());
143                return b.isEquals();
144        }
145
146        @Override
147        public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
148                ResourceIndexedComboTokenNonUnique source = (ResourceIndexedComboTokenNonUnique) theSource;
149                myPartitionSettings = source.myPartitionSettings;
150                myHashComplete = source.myHashComplete;
151                myIndexString = source.myIndexString;
152        }
153
154        @Override
155        public void setResourceId(Long theResourceId) {
156                myResourceId = theResourceId;
157        }
158
159        @Override
160        public Long getId() {
161                return myId;
162        }
163
164        @Override
165        public void setId(Long theId) {
166                myId = theId;
167        }
168
169        @Override
170        public void clearHashes() {
171                myHashComplete = null;
172        }
173
174        @Override
175        public void calculateHashes() {
176                if (myHashComplete != null) {
177                        return;
178                }
179
180                PartitionSettings partitionSettings = getPartitionSettings();
181                PartitionablePartitionId partitionId = getPartitionId();
182                String queryString = myIndexString;
183                setHashComplete(calculateHashComplete(partitionSettings, partitionId, queryString));
184        }
185
186        @Override
187        public int hashCode() {
188                calculateHashes();
189
190                HashCodeBuilder builder = new HashCodeBuilder(17, 37);
191                builder.append(getHashComplete());
192                return builder.toHashCode();
193        }
194
195        public PartitionSettings getPartitionSettings() {
196                return myPartitionSettings;
197        }
198
199        public void setPartitionSettings(PartitionSettings thePartitionSettings) {
200                myPartitionSettings = thePartitionSettings;
201        }
202
203        @Override
204        public ResourceTable getResource() {
205                return myResource;
206        }
207
208        @Override
209        public void setResource(ResourceTable theResource) {
210                myResource = theResource;
211        }
212
213        public Long getHashComplete() {
214                return myHashComplete;
215        }
216
217        public void setHashComplete(Long theHashComplete) {
218                myHashComplete = theHashComplete;
219        }
220
221        @Override
222        public int compareTo(ResourceIndexedComboTokenNonUnique theO) {
223                CompareToBuilder b = new CompareToBuilder();
224                b.append(myHashComplete, theO.getHashComplete());
225                return b.toComparison();
226        }
227
228        @Override
229        public String toString() {
230                return new ToStringBuilder(this)
231                                .append("id", myId)
232                                .append("resourceId", myResourceId)
233                                .append("hashComplete", myHashComplete)
234                                .append("indexString", myIndexString)
235                                .toString();
236        }
237
238        public static long calculateHashComplete(
239                        PartitionSettings partitionSettings, PartitionablePartitionId thePartitionId, String queryString) {
240                RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(thePartitionId);
241                return SearchParamHash.hashSearchParam(partitionSettings, requestPartitionId, queryString);
242        }
243}