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.i18n.Msg;
023import ca.uhn.fhir.interceptor.model.RequestPartitionId;
024import ca.uhn.fhir.jpa.model.config.PartitionSettings;
025import ca.uhn.fhir.jpa.model.util.SearchParamHash;
026import ca.uhn.fhir.model.api.IQueryParameterType;
027import ca.uhn.fhir.rest.api.Constants;
028import jakarta.persistence.Column;
029import jakarta.persistence.MappedSuperclass;
030import jakarta.persistence.Temporal;
031import jakarta.persistence.TemporalType;
032import jakarta.persistence.Transient;
033import org.apache.commons.lang3.Strings;
034import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
035import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
036
037import java.io.Serial;
038import java.util.Date;
039import java.util.List;
040
041import static org.apache.commons.lang3.StringUtils.isNotBlank;
042
043@MappedSuperclass
044public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
045        static final int MAX_SP_NAME = 100;
046
047        @Serial
048        private static final long serialVersionUID = 1L;
049
050        @GenericField
051        @Column(name = "SP_MISSING", nullable = false)
052        private boolean myMissing = false;
053
054        @FullTextField
055        @Column(name = "SP_NAME", length = MAX_SP_NAME)
056        private String myParamName;
057
058        /**
059         * This is just a place to stash the {@link #myParamName} value if
060         * {@link #optimizeIndexStorage()} is called.
061         */
062        @Transient
063        private transient String myParamNameCached;
064
065        @Column(name = "RES_ID", insertable = false, updatable = false, nullable = false)
066        private Long myResourcePid;
067
068        @FullTextField
069        @Column(name = "RES_TYPE", length = Constants.MAX_RESOURCE_NAME_LENGTH)
070        private String myResourceType;
071
072        /**
073         * Composite of resourceType, paramName, and partition info if configured.
074         * Combined with the various date fields for a query.
075         * Nullable to allow optimized storage.
076         */
077        @Column(name = "HASH_IDENTITY", nullable = true)
078        protected Long myHashIdentity;
079
080        @GenericField
081        @Column(name = "SP_UPDATED", nullable = true)
082        @Temporal(TemporalType.TIMESTAMP)
083        private Date myUpdated;
084
085        @Transient
086        private transient PartitionSettings myPartitionSettings;
087
088        @Transient
089        private transient StorageSettings myStorageSettings;
090
091        @Override
092        public abstract Long getId();
093
094        public String getParamName() {
095                if (myParamNameCached != null) {
096                        return myParamNameCached;
097                }
098                return myParamName;
099        }
100
101        public void setParamName(String theName) {
102                if (!Strings.CS.equals(myParamName, theName)) {
103                        myParamName = theName;
104                        clearHashes();
105                }
106        }
107
108        /**
109         * Restore SP_NAME without clearing hashes
110         */
111        public void restoreParamName(String theParamName) {
112                if (myParamName == null) {
113                        myParamName = theParamName;
114                }
115        }
116
117        /**
118         * Set SP_NAME, RES_TYPE to null without clearing hashes
119         */
120        public void optimizeIndexStorage() {
121                if (isNotBlank(myParamName)) {
122                        myParamNameCached = myParamName;
123                        myParamName = null;
124                }
125                myResourceType = null;
126        }
127
128        public boolean isIndexStorageOptimized() {
129                return myParamName == null || myResourceType == null;
130        }
131
132        // MB pushed these down to the individual SP classes so we could name the FK in the join annotation
133        /**
134         * Get the Resource this SP indexes
135         */
136        public abstract ResourceTable getResource();
137
138        public abstract BaseResourceIndexedSearchParam setResource(ResourceTable theResource);
139
140        @Override
141        public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
142                BaseResourceIndexedSearchParam source = (BaseResourceIndexedSearchParam) theSource;
143                myMissing = source.myMissing;
144                myParamName = source.myParamName;
145                myResourceType = source.myResourceType;
146                myUpdated = source.myUpdated;
147                myStorageSettings = source.myStorageSettings;
148                myPartitionSettings = source.myPartitionSettings;
149                setPartitionId(source.getPartitionId());
150        }
151
152        public Long getResourcePid() {
153                return myResourcePid;
154        }
155
156        public String getResourceType() {
157                return myResourceType;
158        }
159
160        public void setResourceType(String theResourceType) {
161                myResourceType = theResourceType;
162        }
163
164        public void setHashIdentity(Long theHashIdentity) {
165                myHashIdentity = theHashIdentity;
166        }
167
168        public Long getHashIdentity() {
169                return myHashIdentity;
170        }
171
172        public Date getUpdated() {
173                return myUpdated;
174        }
175
176        public void setUpdated(Date theUpdated) {
177                myUpdated = theUpdated;
178        }
179
180        public boolean isMissing() {
181                return myMissing;
182        }
183
184        public BaseResourceIndexedSearchParam setMissing(boolean theMissing) {
185                myMissing = theMissing;
186                return this;
187        }
188
189        public abstract IQueryParameterType toQueryParameterType();
190
191        public boolean matches(IQueryParameterType theParam) {
192                throw new UnsupportedOperationException(Msg.code(1526) + "No parameter matcher for " + theParam);
193        }
194
195        public PartitionSettings getPartitionSettings() {
196                return myPartitionSettings;
197        }
198
199        public BaseResourceIndexedSearchParam setPartitionSettings(PartitionSettings thePartitionSettings) {
200                myPartitionSettings = thePartitionSettings;
201                return this;
202        }
203
204        public StorageSettings getStorageSettings() {
205                return myStorageSettings;
206        }
207
208        public BaseResourceIndexedSearchParam setStorageSettings(StorageSettings theStorageSettings) {
209                myStorageSettings = theStorageSettings;
210                return this;
211        }
212
213        public static long calculateHashIdentity(
214                        PartitionSettings thePartitionSettings,
215                        PartitionablePartitionId theRequestPartitionId,
216                        String theResourceType,
217                        String theParamName) {
218                RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
219                return calculateHashIdentity(thePartitionSettings, requestPartitionId, theResourceType, theParamName);
220        }
221
222        public static long calculateHashIdentity(
223                        PartitionSettings thePartitionSettings,
224                        RequestPartitionId theRequestPartitionId,
225                        String theResourceType,
226                        String theParamName) {
227                return SearchParamHash.hashSearchParam(
228                                thePartitionSettings, theRequestPartitionId, theResourceType, theParamName);
229        }
230
231        public static long calculateHashIdentity(
232                        PartitionSettings thePartitionSettings,
233                        RequestPartitionId theRequestPartitionId,
234                        String theResourceType,
235                        String theParamName,
236                        List<String> theAdditionalValues) {
237                String[] values = new String[theAdditionalValues.size() + 2];
238                values[0] = theResourceType;
239                values[1] = theParamName;
240                for (int i = 0; i < theAdditionalValues.size(); i++) {
241                        values[i + 2] = theAdditionalValues.get(i);
242                }
243
244                return SearchParamHash.hashSearchParam(thePartitionSettings, theRequestPartitionId, values);
245        }
246}