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