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.context.ParserOptions;
025import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
026import com.google.common.annotations.VisibleForTesting;
027import org.apache.commons.lang3.Validate;
028import org.hl7.fhir.dstu2.model.Subscription;
029import org.hl7.fhir.instance.model.api.IPrimitiveType;
030import org.hl7.fhir.r4.model.DateTimeType;
031
032import java.util.Arrays;
033import java.util.Collections;
034import java.util.Date;
035import java.util.HashMap;
036import java.util.HashSet;
037import java.util.Map;
038import java.util.Set;
039
040import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
041
042// TODO: move this to ca.uhn.fhir.jpa.model.config
043public class ModelConfig {
044
045        /**
046         * Default {@link #getTreatReferencesAsLogical() logical URL bases}. Includes the following
047         * values:
048         * <ul>
049         * <li><code>"http://hl7.org/fhir/valueset-*"</code></li>
050         * <li><code>"http://hl7.org/fhir/codesystem-*"</code></li>
051         * <li><code>"http://hl7.org/fhir/StructureDefinition/*"</code></li>
052         * </ul>
053         */
054        public static final Set<String> DEFAULT_LOGICAL_BASE_URLS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("http://hl7.org/fhir/ValueSet/*", "http://hl7.org/fhir/CodeSystem/*", "http://hl7.org/fhir/valueset-*", "http://hl7.org/fhir/codesystem-*", "http://hl7.org/fhir/StructureDefinition/*")));
055
056        public static final String DEFAULT_WEBSOCKET_CONTEXT_PATH = "/websocket";
057
058        /*
059         * <p>
060         * Note the following database documented limitations:
061         *    <ul>
062         *       <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
063         *       <li>MySQL 8: the range for DATETIME values is '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999`</li>
064         *       <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
065         *       <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
066         *       <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
067         *     </ul>
068         * </p>
069         */
070        protected static final String DEFAULT_PERIOD_INDEX_START_OF_TIME = "1001-01-01";
071        protected static final String DEFAULT_PERIOD_INDEX_END_OF_TIME = "9000-01-01";
072        /**
073         * update setter javadoc if default changes
074         */
075        private boolean myAllowContainsSearches = false;
076        private boolean myAllowExternalReferences = false;
077        private Set<String> myTreatBaseUrlsAsLocal = new HashSet<>();
078        private Set<String> myTreatReferencesAsLogical = new HashSet<>(DEFAULT_LOGICAL_BASE_URLS);
079        private boolean myDefaultSearchParamsCanBeOverridden = true;
080        private Set<Subscription.SubscriptionChannelType> mySupportedSubscriptionTypes = new HashSet<>();
081        private boolean myCrossPartitionSubscription = false;
082        private String myEmailFromAddress = "noreply@unknown.com";
083        private String myWebsocketContextPath = DEFAULT_WEBSOCKET_CONTEXT_PATH;
084        /**
085         * Update setter javadoc if default changes.
086         */
087        private boolean myUseOrdinalDatesForDayPrecisionSearches = true;
088        private boolean mySuppressStringIndexingInTokens = false;
089
090        private IPrimitiveType<Date> myPeriodIndexStartOfTime;
091        private IPrimitiveType<Date> myPeriodIndexEndOfTime;
092
093        private NormalizedQuantitySearchLevel myNormalizedQuantitySearchLevel;
094        private Set<String> myAutoVersionReferenceAtPaths = Collections.emptySet();
095        private Map<String, Set<String>> myTypeToAutoVersionReferenceAtPaths = Collections.emptyMap();
096        private boolean myRespectVersionsForSearchIncludes;
097        private boolean myIndexOnContainedResources = false;
098        private boolean myIndexOnContainedResourcesRecursively = false;
099        private boolean myAllowMdmExpansion = false;
100        private boolean myAutoSupportDefaultSearchParams = true;
101        private boolean myIndexIdentifierOfType = false;
102
103        /**
104         * Constructor
105         */
106        public ModelConfig() {
107                setPeriodIndexStartOfTime(new DateTimeType(DEFAULT_PERIOD_INDEX_START_OF_TIME));
108                setPeriodIndexEndOfTime(new DateTimeType(DEFAULT_PERIOD_INDEX_END_OF_TIME));
109                setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
110        }
111
112        /**
113         * If set to <code>true</code> (default is <code>false</code>) the
114         * <code>:of-type</code> modifier on token search parameters for
115         * identifiers will be supported. Enabling this causes additional
116         * indexing overhead (although very minor) so it is disabled unless it is
117         * actually needed.
118         *
119         * @since 5.7.0
120         */
121        public boolean isIndexIdentifierOfType() {
122                return myIndexIdentifierOfType;
123        }
124
125        /**
126         * If set to <code>true</code> (default is <code>false</code>) the
127         * <code>:of-type</code> modifier on token search parameters for
128         * identifiers will be supported. Enabling this causes additional
129         * indexing overhead (although very minor) so it is disabled unless it is
130         * actually needed.
131         *
132         * @since 5.7.0
133         */
134        public void setIndexIdentifierOfType(boolean theIndexIdentifierOfType) {
135                myIndexIdentifierOfType = theIndexIdentifierOfType;
136        }
137
138        /**
139         * If set to {@code true} the default search params (i.e. the search parameters that are
140         * defined by the FHIR specification itself) may be overridden by uploading search
141         * parameters to the server with the same code as the built-in search parameter.
142         * <p>
143         * This can be useful if you want to be able to disable or alter
144         * the behaviour of the default search parameters.
145         * </p>
146         * <p>
147         * The default value for this setting is {@code true}
148         * </p>
149         */
150        public boolean isDefaultSearchParamsCanBeOverridden() {
151                return myDefaultSearchParamsCanBeOverridden;
152        }
153
154        /**
155         * If set to {@code true} the default search params (i.e. the search parameters that are
156         * defined by the FHIR specification itself) may be overridden by uploading search
157         * parameters to the server with the same code as the built-in search parameter.
158         * <p>
159         * This can be useful if you want to be able to disable or alter
160         * the behaviour of the default search parameters.
161         * </p>
162         * <p>
163         * The default value for this setting is {@code true}
164         * </p>
165         */
166        public void setDefaultSearchParamsCanBeOverridden(boolean theDefaultSearchParamsCanBeOverridden) {
167                myDefaultSearchParamsCanBeOverridden = theDefaultSearchParamsCanBeOverridden;
168        }
169
170        /**
171         * If enabled, the server will support the use of :contains searches,
172         * which are helpful but can have adverse effects on performance.
173         * <p>
174         * Default is <code>false</code> (Note that prior to HAPI FHIR
175         * 3.5.0 the default was <code>true</code>)
176         * </p>
177         * <p>
178         * Note: If you change this value after data already has
179         * already been stored in the database, you must for a reindexing
180         * of all data in the database or resources may not be
181         * searchable.
182         * </p>
183         */
184        public boolean isAllowContainsSearches() {
185                return myAllowContainsSearches;
186        }
187
188        /**
189         * If enabled, the server will support the use of :contains searches,
190         * which are helpful but can have adverse effects on performance.
191         * <p>
192         * Default is <code>false</code> (Note that prior to HAPI FHIR
193         * 3.5.0 the default was <code>true</code>)
194         * </p>
195         * <p>
196         * Note: If you change this value after data already has
197         * already been stored in the database, you must for a reindexing
198         * of all data in the database or resources may not be
199         * searchable.
200         * </p>
201         */
202        public void setAllowContainsSearches(boolean theAllowContainsSearches) {
203                this.myAllowContainsSearches = theAllowContainsSearches;
204        }
205
206        /**
207         * If enabled, the server will support the use of :mdm search parameter qualifier on Reference Search Parameters.
208         * This Parameter Qualifier is HAPI-specific, and not defined anywhere in the FHIR specification. Using this qualifier
209         * will result in an MDM expansion being done on the reference, which will expand the search scope. For example, if Patient/1
210         * is MDM-matched to Patient/2 and you execute the search:
211         * Observation?subject:mdm=Patient/1 , you will receive observations for both Patient/1 and Patient/2.
212         * <p>
213         * Default is <code>false</code>
214         * </p>
215         *
216         * @since 5.4.0
217         */
218        public boolean isAllowMdmExpansion() {
219                return myAllowMdmExpansion;
220        }
221
222        /**
223         * If enabled, the server will support the use of :mdm search parameter qualifier on Reference Search Parameters.
224         * This Parameter Qualifier is HAPI-specific, and not defined anywhere in the FHIR specification. Using this qualifier
225         * will result in an MDM expansion being done on the reference, which will expand the search scope. For example, if Patient/1
226         * is MDM-matched to Patient/2 and you execute the search:
227         * Observation?subject:mdm=Patient/1 , you will receive observations for both Patient/1 and Patient/2.
228         * <p>
229         * Default is <code>false</code>
230         * </p>
231         *
232         * @since 5.4.0
233         */
234        public void setAllowMdmExpansion(boolean theAllowMdmExpansion) {
235                myAllowMdmExpansion = theAllowMdmExpansion;
236        }
237
238        /**
239         * If set to <code>true</code> (default is <code>false</code>) the server will allow
240         * resources to have references to external servers. For example if this server is
241         * running at <code>http://example.com/fhir</code> and this setting is set to
242         * <code>true</code> the server will allow a Patient resource to be saved with a
243         * Patient.organization value of <code>http://foo.com/Organization/1</code>.
244         * <p>
245         * Under the default behaviour if this value has not been changed, the above
246         * resource would be rejected by the server because it requires all references
247         * to be resolvable on the local server.
248         * </p>
249         * <p>
250         * Note that external references will be indexed by the server and may be searched
251         * (e.g. <code>Patient:organization</code>), but
252         * chained searches (e.g. <code>Patient:organization.name</code>) will not work across
253         * these references.
254         * </p>
255         * <p>
256         * It is recommended to also set {@link #setTreatBaseUrlsAsLocal(Set)} if this value
257         * is set to <code>true</code>
258         * </p>
259         *
260         * @see #setTreatBaseUrlsAsLocal(Set)
261         * @see #setAllowExternalReferences(boolean)
262         */
263        public boolean isAllowExternalReferences() {
264                return myAllowExternalReferences;
265        }
266
267        /**
268         * If set to <code>true</code> (default is <code>false</code>) the server will allow
269         * resources to have references to external servers. For example if this server is
270         * running at <code>http://example.com/fhir</code> and this setting is set to
271         * <code>true</code> the server will allow a Patient resource to be saved with a
272         * Patient.organization value of <code>http://foo.com/Organization/1</code>.
273         * <p>
274         * Under the default behaviour if this value has not been changed, the above
275         * resource would be rejected by the server because it requires all references
276         * to be resolvable on the local server.
277         * </p>
278         * <p>
279         * Note that external references will be indexed by the server and may be searched
280         * (e.g. <code>Patient:organization</code>), but
281         * chained searches (e.g. <code>Patient:organization.name</code>) will not work across
282         * these references.
283         * </p>
284         * <p>
285         * It is recommended to also set {@link #setTreatBaseUrlsAsLocal(Set)} if this value
286         * is set to <code>true</code>
287         * </p>
288         *
289         * @see #setTreatBaseUrlsAsLocal(Set)
290         * @see #setAllowExternalReferences(boolean)
291         */
292        public void setAllowExternalReferences(boolean theAllowExternalReferences) {
293                myAllowExternalReferences = theAllowExternalReferences;
294        }
295
296        /**
297         * This setting may be used to advise the server that any references found in
298         * resources that have any of the base URLs given here will be replaced with
299         * simple local references.
300         * <p>
301         * For example, if the set contains the value <code>http://example.com/base/</code>
302         * and a resource is submitted to the server that contains a reference to
303         * <code>http://example.com/base/Patient/1</code>, the server will automatically
304         * convert this reference to <code>Patient/1</code>
305         * </p>
306         * <p>
307         * Note that this property has different behaviour from {@link ModelConfig#getTreatReferencesAsLogical()}
308         * </p>
309         *
310         * @see #getTreatReferencesAsLogical()
311         */
312        public Set<String> getTreatBaseUrlsAsLocal() {
313                return myTreatBaseUrlsAsLocal;
314        }
315
316        /**
317         * This setting may be used to advise the server that any references found in
318         * resources that have any of the base URLs given here will be replaced with
319         * simple local references.
320         * <p>
321         * For example, if the set contains the value <code>http://example.com/base/</code>
322         * and a resource is submitted to the server that contains a reference to
323         * <code>http://example.com/base/Patient/1</code>, the server will automatically
324         * convert this reference to <code>Patient/1</code>
325         * </p>
326         *
327         * @param theTreatBaseUrlsAsLocal The set of base URLs. May be <code>null</code>, which
328         *                                means no references will be treated as external
329         */
330        public void setTreatBaseUrlsAsLocal(Set<String> theTreatBaseUrlsAsLocal) {
331                if (theTreatBaseUrlsAsLocal != null) {
332                        for (String next : theTreatBaseUrlsAsLocal) {
333                                validateTreatBaseUrlsAsLocal(next);
334                        }
335                }
336
337                HashSet<String> treatBaseUrlsAsLocal = new HashSet<>();
338                for (String next : defaultIfNull(theTreatBaseUrlsAsLocal, new HashSet<String>())) {
339                        while (next.endsWith("/")) {
340                                next = next.substring(0, next.length() - 1);
341                        }
342                        treatBaseUrlsAsLocal.add(next);
343                }
344                myTreatBaseUrlsAsLocal = treatBaseUrlsAsLocal;
345        }
346
347        /**
348         * Add a value to the {@link #setTreatReferencesAsLogical(Set) logical references list}.
349         *
350         * @see #setTreatReferencesAsLogical(Set)
351         */
352        public void addTreatReferencesAsLogical(String theTreatReferencesAsLogical) {
353                validateTreatBaseUrlsAsLocal(theTreatReferencesAsLogical);
354
355                if (myTreatReferencesAsLogical == null) {
356                        myTreatReferencesAsLogical = new HashSet<>();
357                }
358                myTreatReferencesAsLogical.add(theTreatReferencesAsLogical);
359        }
360
361        /**
362         * This setting may be used to advise the server that any references found in
363         * resources that have any of the base URLs given here will be treated as logical
364         * references instead of being treated as real references.
365         * <p>
366         * A logical reference is a reference which is treated as an identifier, and
367         * does not neccesarily resolve. See <a href="http://hl7.org/fhir/references.html">references</a> for
368         * a description of logical references. For example, the valueset
369         * <a href="http://hl7.org/fhir/valueset-quantity-comparator.html">valueset-quantity-comparator</a> is a logical
370         * reference.
371         * </p>
372         * <p>
373         * Values for this field may take either of the following forms:
374         * </p>
375         * <ul>
376         * <li><code>http://example.com/some-url</code> <b>(will be matched exactly)</b></li>
377         * <li><code>http://example.com/some-base*</code> <b>(will match anything beginning with the part before the *)</b></li>
378         * </ul>
379         *
380         * @see #DEFAULT_LOGICAL_BASE_URLS Default values for this property
381         */
382        public Set<String> getTreatReferencesAsLogical() {
383                return myTreatReferencesAsLogical;
384        }
385
386        /**
387         * This setting may be used to advise the server that any references found in
388         * resources that have any of the base URLs given here will be treated as logical
389         * references instead of being treated as real references.
390         * <p>
391         * A logical reference is a reference which is treated as an identifier, and
392         * does not neccesarily resolve. See <a href="http://hl7.org/fhir/references.html">references</a> for
393         * a description of logical references. For example, the valueset
394         * <a href="http://hl7.org/fhir/valueset-quantity-comparator.html">valueset-quantity-comparator</a> is a logical
395         * reference.
396         * </p>
397         * <p>
398         * Values for this field may take either of the following forms:
399         * </p>
400         * <ul>
401         * <li><code>http://example.com/some-url</code> <b>(will be matched exactly)</b></li>
402         * <li><code>http://example.com/some-base*</code> <b>(will match anything beginning with the part before the *)</b></li>
403         * </ul>
404         *
405         * @see #DEFAULT_LOGICAL_BASE_URLS Default values for this property
406         */
407        public ModelConfig setTreatReferencesAsLogical(Set<String> theTreatReferencesAsLogical) {
408                myTreatReferencesAsLogical = theTreatReferencesAsLogical;
409                return this;
410        }
411
412
413        /**
414         * This setting indicates which subscription channel types are supported by the server.  Any subscriptions submitted
415         * to the server matching these types will be activated.
416         */
417        public ModelConfig addSupportedSubscriptionType(Subscription.SubscriptionChannelType theSubscriptionChannelType) {
418                mySupportedSubscriptionTypes.add(theSubscriptionChannelType);
419                return this;
420        }
421
422        /**
423         * This setting indicates which subscription channel types are supported by the server.  Any subscriptions submitted
424         * to the server matching these types will be activated.
425         */
426        public Set<Subscription.SubscriptionChannelType> getSupportedSubscriptionTypes() {
427                return Collections.unmodifiableSet(mySupportedSubscriptionTypes);
428        }
429
430        @VisibleForTesting
431        public void clearSupportedSubscriptionTypesForUnitTest() {
432                mySupportedSubscriptionTypes.clear();
433        }
434
435        /**
436         * If e-mail subscriptions are supported, the From address used when sending e-mails
437         */
438
439        public String getEmailFromAddress() {
440                return myEmailFromAddress;
441        }
442
443        /**
444         * If e-mail subscriptions are supported, the From address used when sending e-mails
445         */
446
447        public void setEmailFromAddress(String theEmailFromAddress) {
448                myEmailFromAddress = theEmailFromAddress;
449        }
450
451        /**
452         * If websocket subscriptions are enabled, this specifies the context path that listens to them.  Default value "/websocket".
453         */
454
455        public String getWebsocketContextPath() {
456                return myWebsocketContextPath;
457        }
458
459        /**
460         * If websocket subscriptions are enabled, this specifies the context path that listens to them.  Default value "/websocket".
461         */
462
463        public void setWebsocketContextPath(String theWebsocketContextPath) {
464                myWebsocketContextPath = theWebsocketContextPath;
465        }
466
467        /**
468         * <p>
469         * Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
470         * {@link ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
471         * precision of {@link TemporalPrecisionEnum#DAY}.
472         * <p>
473         * For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
474         * integer representing the ordinal date {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
475         * and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
476         * </p>
477         * Default is {@literal true} beginning in HAPI FHIR 5.0.0
478         * </p>
479         *
480         * @since 5.0.0
481         */
482        public boolean getUseOrdinalDatesForDayPrecisionSearches() {
483                return myUseOrdinalDatesForDayPrecisionSearches;
484        }
485
486        /**
487         * <p>
488         * Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
489         * {@link ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
490         * precision of {@link TemporalPrecisionEnum#DAY}.
491         * <p>
492         * For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
493         * ordinal {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
494         * and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
495         * </p>
496         * Default is {@literal true} beginning in HAPI FHIR 5.0.0
497         * </p>
498         *
499         * @since 5.0.0
500         */
501        public void setUseOrdinalDatesForDayPrecisionSearches(boolean theUseOrdinalDates) {
502                myUseOrdinalDatesForDayPrecisionSearches = theUseOrdinalDates;
503        }
504
505        /**
506         * If set to <code>true</code> (default is <code>false</code>), when indexing SearchParameter values for token SearchParameter,
507         * the string component to support the <code>:text</code> modifier will be disabled. This means that the following fields
508         * will not be indexed for tokens:
509         * <ul>
510         *    <li>CodeableConcept.text</li>
511         *    <li>Coding.display</li>
512         *    <li>Identifier.use.text</li>
513         * </ul>
514         *
515         * @since 5.0.0
516         */
517        public boolean isSuppressStringIndexingInTokens() {
518                return mySuppressStringIndexingInTokens;
519        }
520
521        /**
522         * If set to <code>true</code> (default is <code>false</code>), when indexing SearchParameter values for token SearchParameter,
523         * the string component to support the <code>:text</code> modifier will be disabled. This means that the following fields
524         * will not be indexed for tokens:
525         * <ul>
526         *    <li>CodeableConcept.text</li>
527         *    <li>Coding.display</li>
528         *    <li>Identifier.use.text</li>
529         * </ul>
530         *
531         * @since 5.0.0
532         */
533        public void setSuppressStringIndexingInTokens(boolean theSuppressStringIndexingInTokens) {
534                mySuppressStringIndexingInTokens = theSuppressStringIndexingInTokens;
535        }
536
537        /**
538         * When indexing a Period (e.g. Encounter.period) where the period has an upper bound
539         * but not a lower bound, a canned "start of time" value can be used as the lower bound
540         * in order to allow range searches to correctly identify all values in the range.
541         * <p>
542         * The default value for this is {@link #DEFAULT_PERIOD_INDEX_START_OF_TIME} which
543         * is probably good enough for almost any application, but this can be changed if
544         * needed.
545         * </p>
546         * <p>
547         * Note the following database documented limitations:
548         *    <ul>
549         *       <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
550         *       <li>MySQL 8: the range for DATETIME values is '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999`</li>
551         *       <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
552         *       <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
553         *       <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
554         *     </ul>
555         * </p>
556         *
557         * @see #getPeriodIndexEndOfTime()
558         * @since 5.1.0
559         */
560        public IPrimitiveType<Date> getPeriodIndexStartOfTime() {
561                return myPeriodIndexStartOfTime;
562        }
563
564        /**
565         * When indexing a Period (e.g. Encounter.period) where the period has an upper bound
566         * but not a lower bound, a canned "start of time" value can be used as the lower bound
567         * in order to allow range searches to correctly identify all values in the range.
568         * <p>
569         * The default value for this is {@link #DEFAULT_PERIOD_INDEX_START_OF_TIME} which
570         * is probably good enough for almost any application, but this can be changed if
571         * needed.
572         * </p>
573         * <p>
574         * Note the following database documented limitations:
575         *    <ul>
576         *       <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
577         *       <li>MySQL 8: the range for DATETIME values is '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999`</li>
578         *       <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
579         *       <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
580         *       <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
581         *     </ul>
582         * </p>
583         *
584         * @see #getPeriodIndexEndOfTime()
585         * @since 5.1.0
586         */
587        public void setPeriodIndexStartOfTime(IPrimitiveType<Date> thePeriodIndexStartOfTime) {
588                Validate.notNull(thePeriodIndexStartOfTime, "thePeriodIndexStartOfTime must not be null");
589                myPeriodIndexStartOfTime = thePeriodIndexStartOfTime;
590        }
591
592        /**
593         * When indexing a Period (e.g. Encounter.period) where the period has a lower bound
594         * but not an upper bound, a canned "end of time" value can be used as the upper bound
595         * in order to allow range searches to correctly identify all values in the range.
596         * <p>
597         * The default value for this is {@link #DEFAULT_PERIOD_INDEX_START_OF_TIME} which
598         * is probably good enough for almost any application, but this can be changed if
599         * needed.
600         * </p>
601         * <p>
602         * Note the following database documented limitations:
603         *    <ul>
604         *       <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
605         *       <li>MySQL 8: the range for DATETIME values is '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999`</li>
606         *       <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
607         *       <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
608         *       <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
609         *     </ul>
610         * </p>
611         *
612         * @see #getPeriodIndexStartOfTime()
613         * @since 5.1.0
614         */
615        public IPrimitiveType<Date> getPeriodIndexEndOfTime() {
616                return myPeriodIndexEndOfTime;
617        }
618
619        /**
620         * When indexing a Period (e.g. Encounter.period) where the period has an upper bound
621         * but not a lower bound, a canned "start of time" value can be used as the lower bound
622         * in order to allow range searches to correctly identify all values in the range.
623         * <p>
624         * The default value for this is {@link #DEFAULT_PERIOD_INDEX_START_OF_TIME} which
625         * is probably good enough for almost any application, but this can be changed if
626         * needed.
627         * </p>
628         * <p>
629         * Note the following database documented limitations:
630         *    <ul>
631         *       <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
632         *       <li>MySQL 8: the range for DATETIME values is '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999`</li>
633         *       <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
634         *       <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
635         *       <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
636         *     </ul>
637         * </p>
638         *
639         * @see #getPeriodIndexStartOfTime()
640         * @since 5.1.0
641         */
642        public void setPeriodIndexEndOfTime(IPrimitiveType<Date> thePeriodIndexEndOfTime) {
643                Validate.notNull(thePeriodIndexEndOfTime, "thePeriodIndexEndOfTime must not be null");
644                myPeriodIndexEndOfTime = thePeriodIndexEndOfTime;
645        }
646
647        /**
648         * Toggles whether Quantity searches support value normalization when using valid UCUM coded values.
649         *
650         * <p>
651         * The default value is {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED} which is current behavior.
652         * </p>
653         * <p>
654         * Here is the UCUM service support level
655         *    <ul>
656         *       <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED}, default, Quantity is stored in {@link ResourceIndexedSearchParamQuantity} only and it is used by searching.</li>
657         *       <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_STORAGE_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, but {@link ResourceIndexedSearchParamQuantity} is used by searching.</li>
658         *       <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching.</li>
659         *     </ul>
660         * </p>
661         *
662         * @since 5.3.0
663         */
664        public NormalizedQuantitySearchLevel getNormalizedQuantitySearchLevel() {
665                return myNormalizedQuantitySearchLevel;
666        }
667
668        /**
669         * Toggles whether Quantity searches support value normalization when using valid UCUM coded values.
670         *
671         * <p>
672         * The default value is {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED} which is current behavior.
673         * </p>
674         * <p>
675         * Here is the UCUM service support level
676         *    <ul>
677         *       <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED}, default, Quantity is stored in {@link ResourceIndexedSearchParamQuantity} only and it is used by searching.</li>
678         *       <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_STORAGE_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, but {@link ResourceIndexedSearchParamQuantity} is used by searching.</li>
679         *       <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching.</li>
680         *     </ul>
681         * </p>
682         *
683         * @since 5.3.0
684         */
685        public void setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel theNormalizedQuantitySearchLevel) {
686                myNormalizedQuantitySearchLevel = theNormalizedQuantitySearchLevel;
687        }
688
689        /**
690         * When set with resource paths (e.g. <code>"Observation.subject"</code>), any references found at the given paths
691         * will automatically have versions appended. The version used will be the current version of the given resource.
692         *
693         * @since 5.3.0
694         */
695        public Set<String> getAutoVersionReferenceAtPaths() {
696                return myAutoVersionReferenceAtPaths;
697        }
698
699        /**
700         * When set with resource paths (e.g. <code>"Observation.subject"</code>), any references found at the given paths
701         * will automatically have versions appended. The version used will be the current version of the given resource.
702         * <p>
703         * Versions will only be added if the reference does not already have a version, so any versioned references
704         * supplied by the client will take precedence over the automatic current version.
705         * </p>
706         * <p>
707         * Note that for this setting to be useful, the {@link ParserOptions}
708         * {@link ParserOptions#getDontStripVersionsFromReferencesAtPaths() DontStripVersionsFromReferencesAtPaths}
709         * option must also be set.
710         * </p>
711         *
712         * @param thePaths A collection of reference paths for which the versions will be appended automatically
713         *                 when serializing, e.g. "Patient.managingOrganization" or "AuditEvent.object.reference". Note that
714         *                 only resource name and field names with dots separating is allowed here (no repetition
715         *                 indicators, FluentPath expressions, etc.)
716         * @since 5.3.0
717         */
718        public void setAutoVersionReferenceAtPaths(String... thePaths) {
719                Set<String> paths = Collections.emptySet();
720                if (thePaths != null) {
721                        paths = new HashSet<>(Arrays.asList(thePaths));
722                }
723                setAutoVersionReferenceAtPaths(paths);
724        }
725
726        /**
727         * When set with resource paths (e.g. <code>"Observation.subject"</code>), any references found at the given paths
728         * will automatically have versions appended. The version used will be the current version of the given resource.
729         * <p>
730         * Versions will only be added if the reference does not already have a version, so any versioned references
731         * supplied by the client will take precedence over the automatic current version.
732         * </p>
733         * <p>
734         * Note that for this setting to be useful, the {@link ParserOptions}
735         * {@link ParserOptions#getDontStripVersionsFromReferencesAtPaths() DontStripVersionsFromReferencesAtPaths}
736         * option must also be set
737         * </p>
738         *
739         * @param thePaths A collection of reference paths for which the versions will be appended automatically
740         *                 when serializing, e.g. "Patient.managingOrganization" or "AuditEvent.object.reference". Note that
741         *                 only resource name and field names with dots separating is allowed here (no repetition
742         *                 indicators, FluentPath expressions, etc.)
743         * @since 5.3.0
744         */
745        public void setAutoVersionReferenceAtPaths(Set<String> thePaths) {
746                Set<String> paths = defaultIfNull(thePaths, Collections.emptySet());
747                Map<String, Set<String>> byType = new HashMap<>();
748                for (String nextPath : paths) {
749                        int doxIdx = nextPath.indexOf('.');
750                        Validate.isTrue(doxIdx > 0, "Invalid path for auto-version reference at path: %s", nextPath);
751                        String type = nextPath.substring(0, doxIdx);
752                        byType.computeIfAbsent(type, t -> new HashSet<>()).add(nextPath);
753                }
754
755
756                myAutoVersionReferenceAtPaths = paths;
757                myTypeToAutoVersionReferenceAtPaths = byType;
758        }
759
760        /**
761         * Returns a sub-collection of {@link #getAutoVersionReferenceAtPaths()} containing only paths
762         * for the given resource type.
763         *
764         * @since 5.3.0
765         */
766        public Set<String> getAutoVersionReferenceAtPathsByResourceType(String theResourceType) {
767                Validate.notEmpty(theResourceType, "theResourceType must not be null or empty");
768                Set<String> retVal = myTypeToAutoVersionReferenceAtPaths.get(theResourceType);
769                retVal = defaultIfNull(retVal, Collections.emptySet());
770                return retVal;
771        }
772
773        /**
774         * Should searches with <code>_include</code> respect versioned references, and pull the specific requested version.
775         * This may have performance impacts on heavily loaded systems.
776         *
777         * @since 5.3.0
778         */
779        public boolean isRespectVersionsForSearchIncludes() {
780                return myRespectVersionsForSearchIncludes;
781        }
782
783        /**
784         * Should searches with <code>_include</code> respect versioned references, and pull the specific requested version.
785         * This may have performance impacts on heavily loaded systems.
786         *
787         * @since 5.3.0
788         */
789        public void setRespectVersionsForSearchIncludes(boolean theRespectVersionsForSearchIncludes) {
790                myRespectVersionsForSearchIncludes = theRespectVersionsForSearchIncludes;
791        }
792
793        /**
794         * Should indexing and searching on contained resources be enabled on this server.
795         * This may have performance impacts, and should be enabled only if it is needed. Default is <code>false</code>.
796         *
797         * @since 5.4.0
798         */
799        public boolean isIndexOnContainedResources() {
800                return myIndexOnContainedResources;
801        }
802
803        /**
804         * Should indexing and searching on contained resources be enabled on this server.
805         * This may have performance impacts, and should be enabled only if it is needed. Default is <code>false</code>.
806         *
807         * @since 5.4.0
808         */
809        public void setIndexOnContainedResources(boolean theIndexOnContainedResources) {
810                myIndexOnContainedResources = theIndexOnContainedResources;
811        }
812
813        /**
814         * Should recursive indexing and searching on contained resources be enabled on this server.
815         * This may have performance impacts, and should be enabled only if it is needed. Default is <code>false</code>.
816         *
817         * @since 5.6.0
818         */
819        public boolean isIndexOnContainedResourcesRecursively() {
820                return myIndexOnContainedResourcesRecursively;
821        }
822
823        /**
824         * Should indexing and searching on contained resources be enabled on this server.
825         * This may have performance impacts, and should be enabled only if it is needed. Default is <code>false</code>.
826         *
827         * @since 5.6.0
828         */
829        public void setIndexOnContainedResourcesRecursively(boolean theIndexOnContainedResourcesRecursively) {
830                myIndexOnContainedResourcesRecursively = theIndexOnContainedResourcesRecursively;
831        }
832
833        /**
834         * If this is disabled by setting this to {@literal false} (default is {@literal true}),
835         * the server will not automatically implement and support search parameters that
836         * are not explcitly created in the repository.
837         * <p>
838         * Disabling this can have a dramatic improvement on performance (especially write performance)
839         * in servers that only need to support a small number of search parameters, or no search parameters at all.
840         * Disabling this obviously reduces the options for searching however.
841         * </p>
842         *
843         * @since 5.7.0
844         */
845        public boolean isAutoSupportDefaultSearchParams() {
846                return myAutoSupportDefaultSearchParams;
847        }
848
849        /**
850         * If this is disabled by setting this to {@literal false} (default is {@literal true}),
851         * the server will not automatically implement and support search parameters that
852         * are not explcitly created in the repository.
853         * <p>
854         * Disabling this can have a dramatic improvement on performance (especially write performance)
855         * in servers that only need to support a small number of search parameters, or no search parameters at all.
856         * Disabling this obviously reduces the options for searching however.
857         * </p>
858         *
859         * @since 5.7.0
860         */
861        public void setAutoSupportDefaultSearchParams(boolean theAutoSupportDefaultSearchParams) {
862                myAutoSupportDefaultSearchParams = theAutoSupportDefaultSearchParams;
863        }
864
865        private static void validateTreatBaseUrlsAsLocal(String theUrl) {
866                Validate.notBlank(theUrl, "Base URL must not be null or empty");
867
868                int starIdx = theUrl.indexOf('*');
869                if (starIdx != -1) {
870                        if (starIdx != theUrl.length() - 1) {
871                                throw new IllegalArgumentException(Msg.code(1525) + "Base URL wildcard character (*) can only appear at the end of the string: " + theUrl);
872                        }
873                }
874
875        }
876
877        /**
878         * If enabled, the server will support cross-partition subscription.
879         * This subscription will be the responsible for all the requests from all the partitions on this server.
880         * For example, if the server has 3 partitions, P1, P2, P3
881         * The subscription will live in the DEFAULT partition. Resource posted to DEFAULT, P1, P2, and P3 will trigger this subscription.
882         * <p>
883         * Default is <code>false</code>
884         * </p>
885         *
886         * @since 7.5.0
887         */
888        public boolean isCrossPartitionSubscription() {
889                return myCrossPartitionSubscription;
890        }
891
892        /**
893         * If enabled, the server will support cross-partition subscription.
894         * This subscription will be the responsible for all the requests from all the partitions on this server.
895         * For example, if the server has 3 partitions, P1, P2, P3
896         * The subscription will live in the DEFAULT partition. Resource posted to DEFAULT, P1, P2, and P3 will trigger this subscription.
897         * <p>
898         * Default is <code>false</code>
899         * </p>
900         *
901         * @since 7.5.0
902         */
903        public void setCrossPartitionSubscription(boolean theAllowCrossPartitionSubscription) {
904                myCrossPartitionSubscription = theAllowCrossPartitionSubscription;
905        }
906
907}