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, SP_UPDATED 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                myUpdated = null;
124        }
125
126        public boolean isIndexStorageOptimized() {
127                return myParamName == null || myResourceType == null || myUpdated == null;
128        }
129
130        // MB pushed these down to the individual SP classes so we could name the FK in the join annotation
131        /**
132         * Get the Resource this SP indexes
133         */
134        public abstract ResourceTable getResource();
135
136        public abstract BaseResourceIndexedSearchParam setResource(ResourceTable theResource);
137
138        @Override
139        public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
140                BaseResourceIndexedSearchParam source = (BaseResourceIndexedSearchParam) theSource;
141                myMissing = source.myMissing;
142                myParamName = source.myParamName;
143                myResourceType = source.myResourceType;
144                myUpdated = source.myUpdated;
145                myStorageSettings = source.myStorageSettings;
146                myPartitionSettings = source.myPartitionSettings;
147                setPartitionId(source.getPartitionId());
148        }
149
150        public Long getResourcePid() {
151                return myResourcePid;
152        }
153
154        public String getResourceType() {
155                return myResourceType;
156        }
157
158        public void setResourceType(String theResourceType) {
159                myResourceType = theResourceType;
160        }
161
162        public void setHashIdentity(Long theHashIdentity) {
163                myHashIdentity = theHashIdentity;
164        }
165
166        public Long getHashIdentity() {
167                return myHashIdentity;
168        }
169
170        public Date getUpdated() {
171                return myUpdated;
172        }
173
174        public void setUpdated(Date theUpdated) {
175                myUpdated = theUpdated;
176        }
177
178        public boolean isMissing() {
179                return myMissing;
180        }
181
182        public BaseResourceIndexedSearchParam setMissing(boolean theMissing) {
183                myMissing = theMissing;
184                return this;
185        }
186
187        public abstract IQueryParameterType toQueryParameterType();
188
189        public boolean matches(IQueryParameterType theParam) {
190                throw new UnsupportedOperationException(Msg.code(1526) + "No parameter matcher for " + theParam);
191        }
192
193        public PartitionSettings getPartitionSettings() {
194                return myPartitionSettings;
195        }
196
197        public BaseResourceIndexedSearchParam setPartitionSettings(PartitionSettings thePartitionSettings) {
198                myPartitionSettings = thePartitionSettings;
199                return this;
200        }
201
202        public StorageSettings getStorageSettings() {
203                return myStorageSettings;
204        }
205
206        public BaseResourceIndexedSearchParam setStorageSettings(StorageSettings theStorageSettings) {
207                myStorageSettings = theStorageSettings;
208                return this;
209        }
210
211        public static long calculateHashIdentity(
212                        PartitionSettings thePartitionSettings,
213                        PartitionablePartitionId theRequestPartitionId,
214                        String theResourceType,
215                        String theParamName) {
216                RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
217                return calculateHashIdentity(thePartitionSettings, requestPartitionId, theResourceType, theParamName);
218        }
219
220        public static long calculateHashIdentity(
221                        PartitionSettings thePartitionSettings,
222                        RequestPartitionId theRequestPartitionId,
223                        String theResourceType,
224                        String theParamName) {
225                return SearchParamHash.hashSearchParam(
226                                thePartitionSettings, theRequestPartitionId, theResourceType, theParamName);
227        }
228
229        public static long calculateHashIdentity(
230                        PartitionSettings thePartitionSettings,
231                        RequestPartitionId theRequestPartitionId,
232                        String theResourceType,
233                        String theParamName,
234                        List<String> theAdditionalValues) {
235                String[] values = new String[theAdditionalValues.size() + 2];
236                values[0] = theResourceType;
237                values[1] = theParamName;
238                for (int i = 0; i < theAdditionalValues.size(); i++) {
239                        values[i + 2] = theAdditionalValues.get(i);
240                }
241
242                return SearchParamHash.hashSearchParam(thePartitionSettings, theRequestPartitionId, values);
243        }
244}