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