001package ca.uhn.fhir.jpa.model.entity;
002
003/*
004 * #%L
005 * HAPI FHIR JPA Model
006 * %%
007 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.interceptor.model.RequestPartitionId;
025import ca.uhn.fhir.jpa.model.config.PartitionSettings;
026import ca.uhn.fhir.model.api.IQueryParameterType;
027import ca.uhn.fhir.rest.api.Constants;
028import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
029import ca.uhn.fhir.util.UrlUtil;
030import com.google.common.base.Charsets;
031import com.google.common.hash.HashCode;
032import com.google.common.hash.HashFunction;
033import com.google.common.hash.Hasher;
034import com.google.common.hash.Hashing;
035import org.apache.commons.lang3.StringUtils;
036import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
037import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
038
039import javax.persistence.Column;
040import javax.persistence.FetchType;
041import javax.persistence.JoinColumn;
042import javax.persistence.ManyToOne;
043import javax.persistence.MappedSuperclass;
044import javax.persistence.Temporal;
045import javax.persistence.TemporalType;
046import javax.persistence.Transient;
047import java.util.Date;
048import java.util.List;
049
050@MappedSuperclass
051public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
052        static final int MAX_SP_NAME = 100;
053        /**
054         * Don't change this without careful consideration. You will break existing hashes!
055         */
056        private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128(0);
057
058        /**
059         * Don't make this public 'cause nobody better be able to modify it!
060         */
061        private static final byte[] DELIMITER_BYTES = "|".getBytes(Charsets.UTF_8);
062        private static final long serialVersionUID = 1L;
063
064        @GenericField
065        @Column(name = "SP_MISSING", nullable = false)
066        private boolean myMissing = false;
067
068        @FullTextField
069        @Column(name = "SP_NAME", length = MAX_SP_NAME, nullable = false)
070        private String myParamName;
071
072        @ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
073        @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false)
074        private ResourceTable myResource;
075
076        @Column(name = "RES_ID", insertable = false, updatable = false, nullable = false)
077        private Long myResourcePid;
078
079        @FullTextField
080        @Column(name = "RES_TYPE", updatable = false, nullable = false, length = Constants.MAX_RESOURCE_NAME_LENGTH)
081        private String myResourceType;
082
083        @GenericField
084        @Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3
085        @Temporal(TemporalType.TIMESTAMP)
086        private Date myUpdated;
087
088        @Transient
089        private transient PartitionSettings myPartitionSettings;
090
091        @Transient
092        private transient ModelConfig myModelConfig;
093
094        @Override
095        public abstract Long getId();
096
097        public String getParamName() {
098                return myParamName;
099        }
100
101        public void setParamName(String theName) {
102                if (!StringUtils.equals(myParamName, theName)) {
103                        myParamName = theName;
104                        clearHashes();
105                }
106        }
107
108        public ResourceTable getResource() {
109                return myResource;
110        }
111
112        public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
113                myResource = theResource;
114                myResourceType = theResource.getResourceType();
115                return this;
116        }
117
118        @Override
119        public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
120                BaseResourceIndexedSearchParam source = (BaseResourceIndexedSearchParam) theSource;
121                myMissing = source.myMissing;
122                myParamName = source.myParamName;
123                myUpdated = source.myUpdated;
124                myModelConfig = source.myModelConfig;
125                myPartitionSettings = source.myPartitionSettings;
126                setPartitionId(source.getPartitionId());
127        }
128
129        public Long getResourcePid() {
130                return myResourcePid;
131        }
132
133        public String getResourceType() {
134                return myResourceType;
135        }
136
137        public void setResourceType(String theResourceType) {
138                myResourceType = theResourceType;
139        }
140
141        public Date getUpdated() {
142                return myUpdated;
143        }
144
145        public void setUpdated(Date theUpdated) {
146                myUpdated = theUpdated;
147        }
148
149        public boolean isMissing() {
150                return myMissing;
151        }
152
153        public BaseResourceIndexedSearchParam setMissing(boolean theMissing) {
154                myMissing = theMissing;
155                return this;
156        }
157
158        public abstract IQueryParameterType toQueryParameterType();
159
160        public boolean matches(IQueryParameterType theParam) {
161                throw new UnsupportedOperationException(Msg.code(1526) + "No parameter matcher for " + theParam);
162        }
163
164        public PartitionSettings getPartitionSettings() {
165                return myPartitionSettings;
166        }
167
168        public BaseResourceIndexedSearchParam setPartitionSettings(PartitionSettings thePartitionSettings) {
169                myPartitionSettings = thePartitionSettings;
170                return this;
171        }
172
173        public ModelConfig getModelConfig() {
174                return myModelConfig;
175        }
176
177        public BaseResourceIndexedSearchParam setModelConfig(ModelConfig theModelConfig) {
178                myModelConfig = theModelConfig;
179                return this;
180        }
181
182        public static long calculateHashIdentity(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName) {
183                RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
184                return calculateHashIdentity(thePartitionSettings, requestPartitionId, theResourceType, theParamName);
185        }
186
187        public static long calculateHashIdentity(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName) {
188                return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName);
189        }
190
191        public static long calculateHashIdentity(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, List<String> theAdditionalValues) {
192                String[] values = new String[theAdditionalValues.size() + 2];
193                values[0] = theResourceType;
194                values[1] = theParamName;
195                for (int i = 0; i < theAdditionalValues.size(); i++) {
196                        values[i + 2] = theAdditionalValues.get(i);
197                }
198
199                return hash(thePartitionSettings, theRequestPartitionId, values);
200        }
201
202        /**
203         * Applies a fast and consistent hashing algorithm to a set of strings
204         */
205        static long hash(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String... theValues) {
206                Hasher hasher = HASH_FUNCTION.newHasher();
207
208                if (thePartitionSettings.isPartitioningEnabled() && thePartitionSettings.isIncludePartitionInSearchHashes() && theRequestPartitionId != null) {
209                        if (theRequestPartitionId.getPartitionIds().size() > 1) {
210                                throw new InternalErrorException(Msg.code(1527) + "Can not search multiple partitions when partitions are included in search hashes");
211                        }
212                        Integer partitionId = theRequestPartitionId.getFirstPartitionIdOrNull();
213                        if (partitionId != null) {
214                                hasher.putInt(partitionId);
215                        }
216                }
217
218                for (String next : theValues) {
219                        if (next == null) {
220                                hasher.putByte((byte) 0);
221                        } else {
222                                next = UrlUtil.escapeUrlParam(next);
223                                byte[] bytes = next.getBytes(Charsets.UTF_8);
224                                hasher.putBytes(bytes);
225                        }
226                        hasher.putBytes(DELIMITER_BYTES);
227                }
228
229                HashCode hashCode = hasher.hash();
230                long retVal = hashCode.asLong();
231                return retVal;
232        }
233}