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