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