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.listener.IndexStorageOptimizationListener;
025import ca.uhn.fhir.model.api.IQueryParameterType;
026import ca.uhn.fhir.rest.param.UriParam;
027import jakarta.persistence.Column;
028import jakarta.persistence.Entity;
029import jakarta.persistence.EntityListeners;
030import jakarta.persistence.FetchType;
031import jakarta.persistence.ForeignKey;
032import jakarta.persistence.GeneratedValue;
033import jakarta.persistence.GenerationType;
034import jakarta.persistence.Id;
035import jakarta.persistence.IdClass;
036import jakarta.persistence.Index;
037import jakarta.persistence.JoinColumn;
038import jakarta.persistence.JoinColumns;
039import jakarta.persistence.ManyToOne;
040import jakarta.persistence.Table;
041import org.apache.commons.lang3.StringUtils;
042import org.apache.commons.lang3.builder.EqualsBuilder;
043import org.apache.commons.lang3.builder.HashCodeBuilder;
044import org.apache.commons.lang3.builder.ToStringBuilder;
045import org.hibernate.annotations.GenericGenerator;
046import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
047
048import static ca.uhn.fhir.jpa.model.util.SearchParamHash.hashSearchParam;
049import static org.apache.commons.lang3.StringUtils.defaultString;
050
051@EntityListeners(IndexStorageOptimizationListener.class)
052@Entity
053@Table(
054                name = "HFJ_SPIDX_URI",
055                indexes = {
056                        // for queries
057                        @Index(name = "IDX_SP_URI_HASH_URI_V2", columnList = "HASH_URI,RES_ID,PARTITION_ID"),
058                        // for sorting
059                        @Index(name = "IDX_SP_URI_HASH_IDENTITY_V2", columnList = "HASH_IDENTITY,SP_URI,RES_ID,PARTITION_ID"),
060                        // for index create/delete
061                        @Index(name = "IDX_SP_URI_COORDS", columnList = "RES_ID")
062                })
063@IdClass(IdAndPartitionId.class)
064public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchParam {
065
066        /*
067         * Be careful when modifying this value
068         * MySQL chokes on indexes with combined column length greater than 3052 bytes (768 chars)
069         * https://dev.mysql.com/doc/refman/8.0/en/innodb-limits.html
070         */
071        public static final int MAX_LENGTH = 500;
072
073        private static final long serialVersionUID = 1L;
074
075        @Column(name = "SP_URI", nullable = true, length = MAX_LENGTH)
076        @FullTextField
077        public String myUri;
078
079        @Id
080        @GenericGenerator(name = "SEQ_SPIDX_URI", type = ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator.class)
081        @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_URI")
082        @Column(name = "SP_ID")
083        private Long myId;
084
085        /**
086         * @since 3.4.0 - At some point this should be made not-null
087         */
088        @Column(name = "HASH_URI", nullable = true)
089        private Long myHashUri;
090
091        @ManyToOne(
092                        optional = false,
093                        fetch = FetchType.LAZY,
094                        cascade = {})
095        @JoinColumns(
096                        value = {
097                                @JoinColumn(
098                                                name = "RES_ID",
099                                                referencedColumnName = "RES_ID",
100                                                insertable = false,
101                                                updatable = false,
102                                                nullable = false),
103                                @JoinColumn(
104                                                name = "PARTITION_ID",
105                                                referencedColumnName = "PARTITION_ID",
106                                                insertable = false,
107                                                updatable = false,
108                                                nullable = false)
109                        },
110                        foreignKey = @ForeignKey(name = "FKGXSREUTYMMFJUWDSWV3Y887DO"))
111        private ResourceTable myResource;
112
113        @Column(name = "RES_ID", nullable = false)
114        private Long myResourceId;
115
116        /**
117         * Constructor
118         */
119        public ResourceIndexedSearchParamUri() {
120                super();
121        }
122
123        /**
124         * Constructor
125         */
126        public ResourceIndexedSearchParamUri(
127                        PartitionSettings thePartitionSettings, String theResourceType, String theParamName, String theUri) {
128                setPartitionSettings(thePartitionSettings);
129                setResourceType(theResourceType);
130                setParamName(theParamName);
131                setUri(theUri);
132                calculateHashes();
133        }
134
135        @Override
136        public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
137                super.copyMutableValuesFrom(theSource);
138                ResourceIndexedSearchParamUri source = (ResourceIndexedSearchParamUri) theSource;
139                myUri = source.myUri;
140                myHashUri = source.myHashUri;
141                myHashIdentity = source.myHashIdentity;
142        }
143
144        @Override
145        public void setResourceId(Long theResourceId) {
146                myResourceId = theResourceId;
147        }
148
149        @Override
150        public void clearHashes() {
151                myHashIdentity = null;
152                myHashUri = null;
153        }
154
155        @Override
156        public void calculateHashes() {
157                if (myHashIdentity != null || myHashUri != null) {
158                        return;
159                }
160
161                String resourceType = getResourceType();
162                String paramName = getParamName();
163                String uri = getUri();
164                setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
165                setHashUri(calculateHashUri(getPartitionSettings(), getPartitionId(), resourceType, paramName, uri));
166        }
167
168        @Override
169        public boolean equals(Object theObj) {
170                if (this == theObj) {
171                        return true;
172                }
173                if (theObj == null) {
174                        return false;
175                }
176                if (!(theObj instanceof ResourceIndexedSearchParamUri)) {
177                        return false;
178                }
179                ResourceIndexedSearchParamUri obj = (ResourceIndexedSearchParamUri) theObj;
180                EqualsBuilder b = new EqualsBuilder();
181                b.append(getUri(), obj.getUri());
182                b.append(getHashUri(), obj.getHashUri());
183                b.append(getHashIdentity(), obj.getHashIdentity());
184                b.append(isMissing(), obj.isMissing());
185                return b.isEquals();
186        }
187
188        public Long getHashUri() {
189                return myHashUri;
190        }
191
192        public void setHashUri(Long theHashUri) {
193                myHashUri = theHashUri;
194        }
195
196        @Override
197        public Long getId() {
198                return myId;
199        }
200
201        @Override
202        public void setId(Long theId) {
203                myId = theId;
204        }
205
206        public String getUri() {
207                return myUri;
208        }
209
210        public ResourceIndexedSearchParamUri setUri(String theUri) {
211                myUri = StringUtils.defaultIfBlank(theUri, null);
212                return this;
213        }
214
215        @Override
216        public int hashCode() {
217                HashCodeBuilder b = new HashCodeBuilder();
218                b.append(getUri());
219                b.append(getHashUri());
220                b.append(getHashIdentity());
221                b.append(isMissing());
222                return b.toHashCode();
223        }
224
225        @Override
226        public IQueryParameterType toQueryParameterType() {
227                return new UriParam(getUri());
228        }
229
230        @Override
231        public String toString() {
232                ToStringBuilder b = new ToStringBuilder(this);
233                b.append("id", getId());
234                b.append("resourceId", getResourcePid());
235                b.append("paramName", getParamName());
236                b.append("uri", myUri);
237                b.append("hashUri", myHashUri);
238                b.append("hashIdentity", myHashIdentity);
239                return b.toString();
240        }
241
242        @Override
243        public boolean matches(IQueryParameterType theParam) {
244                if (!(theParam instanceof UriParam)) {
245                        return false;
246                }
247                UriParam uri = (UriParam) theParam;
248                return defaultString(getUri()).equalsIgnoreCase(uri.getValueNotNull());
249        }
250
251        public static long calculateHashUri(
252                        PartitionSettings thePartitionSettings,
253                        PartitionablePartitionId theRequestPartitionId,
254                        String theResourceType,
255                        String theParamName,
256                        String theUri) {
257                RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
258                return calculateHashUri(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theUri);
259        }
260
261        public static long calculateHashUri(
262                        PartitionSettings thePartitionSettings,
263                        RequestPartitionId theRequestPartitionId,
264                        String theResourceType,
265                        String theParamName,
266                        String theUri) {
267                return hashSearchParam(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theUri);
268        }
269
270        @Override
271        public ResourceTable getResource() {
272                return myResource;
273        }
274
275        @Override
276        public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
277                setResourceType(theResource.getResourceType());
278                return this;
279        }
280}