001/*-
002 * #%L
003 * HAPI FHIR JPA Model
004 * %%
005 * Copyright (C) 2014 - 2023 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.context.ParserOptions;
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.jpa.model.dialect.ISequenceValueMassager;
025import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
026import ca.uhn.fhir.rest.server.interceptor.ResponseTerminologyTranslationSvc;
027import ca.uhn.fhir.util.HapiExtensions;
028import com.google.common.annotations.VisibleForTesting;
029import org.apache.commons.collections4.CollectionUtils;
030import org.apache.commons.lang3.Validate;
031import org.hl7.fhir.dstu2.model.Subscription;
032import org.hl7.fhir.instance.model.api.IPrimitiveType;
033import org.hl7.fhir.r4.model.DateTimeType;
034
035import java.util.Arrays;
036import java.util.Collections;
037import java.util.Date;
038import java.util.HashMap;
039import java.util.HashSet;
040import java.util.Map;
041import java.util.Set;
042
043import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
044
045/**
046 * This class contains configuration options common to all hapi-fhir-storage implementations.
047 * Ultimately it should live in that project
048 */
049public class StorageSettings {
050        /**
051         * @since 5.6.0
052         */
053        // Thread Pool size used by batch in bundle
054        public static final int DEFAULT_BUNDLE_BATCH_POOL_SIZE = 20; // 1 for single thread
055
056        public static final int DEFAULT_BUNDLE_BATCH_MAX_POOL_SIZE = 100; // 1 for single thread
057        /**
058         * Default {@link #getTreatReferencesAsLogical() logical URL bases}. Includes the following
059         * values:
060         * <ul>
061         * <li><code>"http://hl7.org/fhir/valueset-*"</code></li>
062         * <li><code>"http://hl7.org/fhir/codesystem-*"</code></li>
063         * <li><code>"http://hl7.org/fhir/StructureDefinition/*"</code></li>
064         * </ul>
065         */
066        public static final Set<String> DEFAULT_LOGICAL_BASE_URLS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
067                        "http://hl7.org/fhir/ValueSet/*",
068                        "http://hl7.org/fhir/CodeSystem/*",
069                        "http://hl7.org/fhir/valueset-*",
070                        "http://hl7.org/fhir/codesystem-*",
071                        "http://hl7.org/fhir/StructureDefinition/*")));
072
073        public static final String DEFAULT_WEBSOCKET_CONTEXT_PATH = "/websocket";
074        /*
075         * <p>
076         * Note the following database documented limitations:
077         *    <ul>
078         *       <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
079         *       <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>
080         *       <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
081         *       <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
082         *       <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
083         *     </ul>
084         * </p>
085         */
086        protected static final String DEFAULT_PERIOD_INDEX_START_OF_TIME = "1001-01-01";
087        protected static final String DEFAULT_PERIOD_INDEX_END_OF_TIME = "9000-01-01";
088        private static final Integer DEFAULT_MAXIMUM_TRANSACTION_BUNDLE_SIZE = null;
089        /**
090         * update setter javadoc if default changes
091         */
092        private boolean myAllowContainsSearches = false;
093
094        private boolean myAllowExternalReferences = false;
095        private Set<String> myTreatBaseUrlsAsLocal = new HashSet<>();
096        private Set<String> myTreatReferencesAsLogical = new HashSet<>(DEFAULT_LOGICAL_BASE_URLS);
097        private boolean myDefaultSearchParamsCanBeOverridden = true;
098        private Set<Subscription.SubscriptionChannelType> mySupportedSubscriptionTypes = new HashSet<>();
099        private boolean myAutoCreatePlaceholderReferenceTargets;
100        private boolean myCrossPartitionSubscriptionEnabled = false;
101        private Integer myBundleBatchPoolSize = DEFAULT_BUNDLE_BATCH_POOL_SIZE;
102        private Integer myBundleBatchMaxPoolSize = DEFAULT_BUNDLE_BATCH_MAX_POOL_SIZE;
103        private boolean myEnableInMemorySubscriptionMatching = true;
104        private boolean myTriggerSubscriptionsForNonVersioningChanges;
105        private boolean myMassIngestionMode;
106        private Integer myMaximumTransactionBundleSize = DEFAULT_MAXIMUM_TRANSACTION_BUNDLE_SIZE;
107        private boolean myNormalizeTerminologyForBulkExportJobs = false;
108        private String myEmailFromAddress = "noreply@unknown.com";
109        private String myWebsocketContextPath = DEFAULT_WEBSOCKET_CONTEXT_PATH;
110        /**
111         * Update setter javadoc if default changes.
112         */
113        private boolean myUseOrdinalDatesForDayPrecisionSearches = true;
114
115        private boolean mySuppressStringIndexingInTokens = false;
116        private Class<? extends ISequenceValueMassager> mySequenceValueMassagerClass;
117        private IPrimitiveType<Date> myPeriodIndexStartOfTime;
118        private IPrimitiveType<Date> myPeriodIndexEndOfTime;
119        private NormalizedQuantitySearchLevel myNormalizedQuantitySearchLevel;
120        private Set<String> myAutoVersionReferenceAtPaths = Collections.emptySet();
121        private Map<String, Set<String>> myTypeToAutoVersionReferenceAtPaths = Collections.emptyMap();
122        private boolean myRespectVersionsForSearchIncludes;
123        private boolean myIndexOnUpliftedRefchains = false;
124        private boolean myIndexOnContainedResources = false;
125        private boolean myIndexOnContainedResourcesRecursively = false;
126        private boolean myAllowMdmExpansion = false;
127        private boolean myAutoSupportDefaultSearchParams = true;
128        private boolean myIndexIdentifierOfType = false;
129        private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED;
130
131        /**
132         * @since 6.8.0
133         * Prevents any non IN-MEMORY Search params from being created by users.
134         */
135        private boolean myAllowOnlyInMemorySubscriptions = false;
136
137        /**
138         * Since 6.4.0
139         */
140        private boolean myQualifySubscriptionMatchingChannelName = true;
141        /**
142         * Should the {@literal _lamguage} SearchParameter be supported
143         * on this server?
144         *
145         * @since 7.0.0
146         */
147        private boolean myLanguageSearchParameterEnabled = false;
148
149        /**
150         * If set to true, the server will prevent the creation of Subscriptions which cannot be evaluated IN-MEMORY. This can improve
151         * overall server performance.
152         *
153         * @since 6.8.0
154         */
155        public void setOnlyAllowInMemorySubscriptions(boolean theAllowOnlyInMemorySearchParams) {
156                myAllowOnlyInMemorySubscriptions = theAllowOnlyInMemorySearchParams;
157        }
158
159        /**
160         * If set to true, the server will prevent the creation of Subscriptions which cannot be evaluated IN-MEMORY. This can improve
161         * overall server performance.
162         *
163         * @since 6.8.0
164         * @return Returns the value of {@link #setOnlyAllowInMemorySubscriptions(boolean)}
165         */
166        public boolean isOnlyAllowInMemorySubscriptions() {
167                return myAllowOnlyInMemorySubscriptions;
168        }
169        /**
170         * Constructor
171         */
172        public StorageSettings() {
173                setSequenceValueMassagerClass(ISequenceValueMassager.NoopSequenceValueMassager.class);
174                setPeriodIndexStartOfTime(new DateTimeType(DEFAULT_PERIOD_INDEX_START_OF_TIME));
175                setPeriodIndexEndOfTime(new DateTimeType(DEFAULT_PERIOD_INDEX_END_OF_TIME));
176                setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
177        }
178
179        /**
180         * When creating or updating a resource: If this property is set to <code>true</code>
181         * (default is <code>false</code>), if the resource has a reference to another resource
182         * on the local server but that reference does not exist, a placeholder resource will be
183         * created.
184         * <p>
185         * In other words, if an observation with subject <code>Patient/FOO</code> is created, but
186         * there is no resource called <code>Patient/FOO</code> on the server, this property causes
187         * an empty patient with ID "FOO" to be created in order to prevent this operation
188         * from failing.
189         * </p>
190         * <p>
191         * This property can be useful in cases where replication between two servers is wanted.
192         * Note however that references containing purely numeric IDs will not be auto-created
193         * as they are never allowed to be client supplied in HAPI FHIR JPA.
194         * <p>
195         * All placeholder resources created in this way have an extension
196         * with the URL {@link HapiExtensions#EXT_RESOURCE_PLACEHOLDER} and the value "true".
197         * </p>
198         */
199        public boolean isAutoCreatePlaceholderReferenceTargets() {
200                return myAutoCreatePlaceholderReferenceTargets;
201        }
202
203        /**
204         * When creating or updating a resource: If this property is set to <code>true</code>
205         * (default is <code>false</code>), if the resource has a reference to another resource
206         * on the local server but that reference does not exist, a placeholder resource will be
207         * created.
208         * <p>
209         * In other words, if an observation with subject <code>Patient/FOO</code> is created, but
210         * there is no resource called <code>Patient/FOO</code> on the server, this property causes
211         * an empty patient with ID "FOO" to be created in order to prevent this operation
212         * from failing.
213         * </p>
214         * <p>
215         * This property can be useful in cases where replication between two servers is wanted.
216         * Note however that references containing purely numeric IDs will not be auto-created
217         * as they are never allowed to be client supplied in HAPI FHIR JPA.
218         * <p>
219         * All placeholder resources created in this way have an extension
220         * with the URL {@link HapiExtensions#EXT_RESOURCE_PLACEHOLDER} and the value "true".
221         * </p>
222         */
223        public void setAutoCreatePlaceholderReferenceTargets(boolean theAutoCreatePlaceholderReferenceTargets) {
224                myAutoCreatePlaceholderReferenceTargets = theAutoCreatePlaceholderReferenceTargets;
225        }
226
227        /**
228         * Get the batch transaction thread pool size.
229         *
230         * @since 5.6.0
231         */
232        public Integer getBundleBatchPoolSize() {
233                return myBundleBatchPoolSize;
234        }
235
236        /**
237         * Set the batch transaction thread pool size. The default is @see {@link #DEFAULT_BUNDLE_BATCH_POOL_SIZE}
238         * set pool size to 1 for single thread
239         *
240         * @since 5.6.0
241         */
242        public void setBundleBatchPoolSize(Integer theBundleBatchPoolSize) {
243                this.myBundleBatchPoolSize = theBundleBatchPoolSize;
244        }
245
246        /**
247         * Get the batch transaction thread max pool size.
248         * set max pool size to 1 for single thread
249         *
250         * @since 5.6.0
251         */
252        public Integer getBundleBatchMaxPoolSize() {
253                return myBundleBatchMaxPoolSize;
254        }
255
256        /**
257         * Set the batch transaction thread pool size. The default is @see {@link #DEFAULT_BUNDLE_BATCH_MAX_POOL_SIZE}
258         *
259         * @since 5.6.0
260         */
261        public void setBundleBatchMaxPoolSize(Integer theBundleBatchMaxPoolSize) {
262                this.myBundleBatchMaxPoolSize = theBundleBatchMaxPoolSize;
263        }
264
265        /**
266         * If set to <code>false</code> (default is true) the server will not use
267         * in-memory subscription searching and instead use the database matcher for all subscription
268         * criteria matching.
269         * <p>
270         * When there are subscriptions registered
271         * on the server, the default behaviour is to compare the changed resource to the
272         * subscription criteria directly in-memory without going out to the database.
273         * Certain types of subscription criteria, e.g. chained references of queries with
274         * qualifiers or prefixes, are not supported by the in-memory matcher and will fall back
275         * to a database matcher.
276         * <p>
277         * The database matcher performs a query against the
278         * database by prepending ?id=XYZ to the subscription criteria where XYZ is the id of the changed entity
279         *
280         * @since 3.6.1
281         */
282        public boolean isEnableInMemorySubscriptionMatching() {
283                return myEnableInMemorySubscriptionMatching;
284        }
285
286        /**
287         * If set to <code>false</code> (default is true) the server will not use
288         * in-memory subscription searching and instead use the database matcher for all subscription
289         * criteria matching.
290         * <p>
291         * When there are subscriptions registered
292         * on the server, the default behaviour is to compare the changed resource to the
293         * subscription criteria directly in-memory without going out to the database.
294         * Certain types of subscription criteria, e.g. chained references of queries with
295         * qualifiers or prefixes, are not supported by the in-memory matcher and will fall back
296         * to a database matcher.
297         * <p>
298         * The database matcher performs a query against the
299         * database by prepending ?id=XYZ to the subscription criteria where XYZ is the id of the changed entity
300         *
301         * @since 3.6.1
302         */
303        public void setEnableInMemorySubscriptionMatching(boolean theEnableInMemorySubscriptionMatching) {
304                myEnableInMemorySubscriptionMatching = theEnableInMemorySubscriptionMatching;
305        }
306
307        /**
308         * If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED})
309         * the server will not create search indexes for search parameters with no values in resources.
310         * <p>
311         * Disabling this feature means that the <code>:missing</code> search modifier will not be
312         * supported on the server, but also means that storage and indexing (i.e. writes to the
313         * database) may be much faster on servers which have lots of search parameters and need
314         * to write quickly.
315         * </p>
316         * <p>
317         * This feature may be enabled on servers where supporting the use of the :missing parameter is
318         * of higher importance than raw write performance
319         * </p>
320         */
321        public IndexEnabledEnum getIndexMissingFields() {
322                return myIndexMissingFieldsEnabled;
323        }
324
325        /**
326         * If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED})
327         * the server will not create search indexes for search parameters with no values in resources.
328         * <p>
329         * Disabling this feature means that the <code>:missing</code> search modifier will not be
330         * supported on the server, but also means that storage and indexing (i.e. writes to the
331         * database) may be much faster on servers which have lots of search parameters and need
332         * to write quickly.
333         * </p>
334         * <p>
335         * This feature may be enabled on servers where supporting the use of the :missing parameter is
336         * of higher importance than raw write performance
337         * </p>
338         * <p>
339         * Note that this setting also has an impact on sorting (i.e. using the
340         * <code>_sort</code> parameter on searches): If the server is configured
341         * to not index missing field.
342         * </p>
343         * <p>
344         * The following index may need to be added into the indexed tables such as <code>HFJ_SPIDX_TOKEN</code>
345         * to improve the search performance while <code>:missing</code> is enabled.
346         * <code>RES_TYPE, SP_NAME, SP_MISSING</code>
347         * </p>
348         */
349        public void setIndexMissingFields(IndexEnabledEnum theIndexMissingFields) {
350                Validate.notNull(theIndexMissingFields, "theIndexMissingFields must not be null");
351                myIndexMissingFieldsEnabled = theIndexMissingFields;
352        }
353
354        /**
355         * If this is enabled (disabled by default), Mass Ingestion Mode is enabled. In this mode, a number of
356         * runtime checks are disabled. This mode is designed for rapid backloading of data while the system is not
357         * being otherwise used.
358         * <p>
359         * In this mode:
360         * <p>
361         * - Tags/Profiles/Security Labels will not be updated on existing resources that already have them
362         * - Resources modification checks will be skipped in favour of a simple hash check
363         * - Extra resource ID caching is enabled
364         *
365         * @since 5.5.0
366         */
367        public boolean isMassIngestionMode() {
368                return myMassIngestionMode;
369        }
370
371        /**
372         * If this is enabled (disabled by default), Mass Ingestion Mode is enabled. In this mode, a number of
373         * runtime checks are disabled. This mode is designed for rapid backloading of data while the system is not
374         * being otherwise used.
375         * <p>
376         * In this mode:
377         * <p>
378         * - Tags/Profiles/Security Labels will not be updated on existing resources that already have them
379         * - Resources modification checks will be skipped in favour of a simple hash check
380         * - Extra resource ID caching is enabled
381         *
382         * @since 5.5.0
383         */
384        public void setMassIngestionMode(boolean theMassIngestionMode) {
385                myMassIngestionMode = theMassIngestionMode;
386        }
387
388        /**
389         * Specifies the maximum number of resources permitted within a single transaction bundle.
390         * If a transaction bundle is submitted with more than this number of resources, it will be
391         * rejected with a PayloadTooLarge exception.
392         * <p>
393         * The default value is <code>null</code>, which means that there is no limit.
394         * </p>
395         */
396        public Integer getMaximumTransactionBundleSize() {
397                return myMaximumTransactionBundleSize;
398        }
399
400        /**
401         * Specifies the maximum number of resources permitted within a single transaction bundle.
402         * If a transaction bundle is submitted with more than this number of resources, it will be
403         * rejected with a PayloadTooLarge exception.
404         * <p>
405         * The default value is <code>null</code>, which means that there is no limit.
406         * </p>
407         */
408        public StorageSettings setMaximumTransactionBundleSize(Integer theMaximumTransactionBundleSize) {
409                myMaximumTransactionBundleSize = theMaximumTransactionBundleSize;
410                return this;
411        }
412
413        /**
414         * If set to true, attempt to map terminology for bulk export jobs using the
415         * logic in
416         * {@link ResponseTerminologyTranslationSvc}. Default is <code>false</code>.
417         *
418         * @since 6.3.0
419         */
420        public boolean isNormalizeTerminologyForBulkExportJobs() {
421                return myNormalizeTerminologyForBulkExportJobs;
422        }
423
424        /**
425         * If set to true, attempt to map terminology for bulk export jobs using the
426         * logic in
427         * {@link ResponseTerminologyTranslationSvc}. Default is <code>false</code>.
428         *
429         * @since 6.3.0
430         */
431        public void setNormalizeTerminologyForBulkExportJobs(boolean theNormalizeTerminologyForBulkExportJobs) {
432                myNormalizeTerminologyForBulkExportJobs = theNormalizeTerminologyForBulkExportJobs;
433        }
434
435        /**
436         * This is an internal API and may change or disappear without notice
437         *
438         * @since 6.2.0
439         */
440        public Class<? extends ISequenceValueMassager> getSequenceValueMassagerClass() {
441                return mySequenceValueMassagerClass;
442        }
443
444        /**
445         * This is an internal API and may change or disappear without notice
446         *
447         * @since 6.2.0
448         */
449        public void setSequenceValueMassagerClass(Class<? extends ISequenceValueMassager> theSequenceValueMassagerClass) {
450                Validate.notNull(theSequenceValueMassagerClass, "theSequenceValueMassagerClass must not be null");
451                mySequenceValueMassagerClass = theSequenceValueMassagerClass;
452        }
453
454        /**
455         * If set to true (default is false) then subscriptions will be triggered for resource updates even if they
456         * do not trigger a new version (e.g. $meta-add and $meta-delete).
457         *
458         * @since 5.5.0
459         */
460        public boolean isTriggerSubscriptionsForNonVersioningChanges() {
461                return myTriggerSubscriptionsForNonVersioningChanges;
462        }
463
464        /**
465         * If set to true (default is false) then subscriptions will be triggered for resource updates even if they
466         * do not trigger a new version (e.g. $meta-add and $meta-delete).
467         *
468         * @since 5.5.0
469         */
470        public void setTriggerSubscriptionsForNonVersioningChanges(boolean theTriggerSubscriptionsForNonVersioningChanges) {
471                myTriggerSubscriptionsForNonVersioningChanges = theTriggerSubscriptionsForNonVersioningChanges;
472        }
473
474        /**
475         * If set to <code>true</code> (default is <code>false</code>) the
476         * <code>:of-type</code> modifier on token search parameters for
477         * identifiers will be supported. Enabling this causes additional
478         * indexing overhead (although very minor) so it is disabled unless it is
479         * actually needed.
480         *
481         * @since 5.7.0
482         */
483        public boolean isIndexIdentifierOfType() {
484                return myIndexIdentifierOfType;
485        }
486
487        /**
488         * If set to <code>true</code> (default is <code>false</code>) the
489         * <code>:of-type</code> modifier on token search parameters for
490         * identifiers will be supported. Enabling this causes additional
491         * indexing overhead (although very minor) so it is disabled unless it is
492         * actually needed.
493         *
494         * @since 5.7.0
495         */
496        public void setIndexIdentifierOfType(boolean theIndexIdentifierOfType) {
497                myIndexIdentifierOfType = theIndexIdentifierOfType;
498        }
499
500        /**
501         * If set to {@code true} the default search params (i.e. the search parameters that are
502         * defined by the FHIR specification itself) may be overridden by uploading search
503         * parameters to the server with the same code as the built-in search parameter.
504         * <p>
505         * This can be useful if you want to be able to disable or alter
506         * the behaviour of the default search parameters.
507         * </p>
508         * <p>
509         * The default value for this setting is {@code true}
510         * </p>
511         */
512        public boolean isDefaultSearchParamsCanBeOverridden() {
513                return myDefaultSearchParamsCanBeOverridden;
514        }
515
516        /**
517         * If set to {@code true} the default search params (i.e. the search parameters that are
518         * defined by the FHIR specification itself) may be overridden by uploading search
519         * parameters to the server with the same code as the built-in search parameter.
520         * <p>
521         * This can be useful if you want to be able to disable or alter
522         * the behaviour of the default search parameters.
523         * </p>
524         * <p>
525         * The default value for this setting is {@code true}
526         * </p>
527         */
528        public void setDefaultSearchParamsCanBeOverridden(boolean theDefaultSearchParamsCanBeOverridden) {
529                myDefaultSearchParamsCanBeOverridden = theDefaultSearchParamsCanBeOverridden;
530        }
531
532        /**
533         * If enabled, the server will support the use of :contains searches,
534         * which are helpful but can have adverse effects on performance.
535         * <p>
536         * Default is <code>false</code> (Note that prior to HAPI FHIR
537         * 3.5.0 the default was <code>true</code>)
538         * </p>
539         * <p>
540         * Note: If you change this value after data already has
541         * already been stored in the database, you must for a reindexing
542         * of all data in the database or resources may not be
543         * searchable.
544         * </p>
545         */
546        public boolean isAllowContainsSearches() {
547                return myAllowContainsSearches;
548        }
549
550        /**
551         * If enabled, the server will support the use of :contains searches,
552         * which are helpful but can have adverse effects on performance.
553         * <p>
554         * Default is <code>false</code> (Note that prior to HAPI FHIR
555         * 3.5.0 the default was <code>true</code>)
556         * </p>
557         * <p>
558         * Note: If you change this value after data already has
559         * already been stored in the database, you must for a reindexing
560         * of all data in the database or resources may not be
561         * searchable.
562         * </p>
563         */
564        public void setAllowContainsSearches(boolean theAllowContainsSearches) {
565                this.myAllowContainsSearches = theAllowContainsSearches;
566        }
567
568        /**
569         * If enabled, the server will support the use of :mdm search parameter qualifier on Reference Search Parameters.
570         * This Parameter Qualifier is HAPI-specific, and not defined anywhere in the FHIR specification. Using this qualifier
571         * will result in an MDM expansion being done on the reference, which will expand the search scope. For example, if Patient/1
572         * is MDM-matched to Patient/2 and you execute the search:
573         * Observation?subject:mdm=Patient/1 , you will receive observations for both Patient/1 and Patient/2.
574         * <p>
575         * Default is <code>false</code>
576         * </p>
577         *
578         * @since 5.4.0
579         */
580        public boolean isAllowMdmExpansion() {
581                return myAllowMdmExpansion;
582        }
583
584        /**
585         * If enabled, the server will support the use of :mdm search parameter qualifier on Reference Search Parameters.
586         * This Parameter Qualifier is HAPI-specific, and not defined anywhere in the FHIR specification. Using this qualifier
587         * will result in an MDM expansion being done on the reference, which will expand the search scope. For example, if Patient/1
588         * is MDM-matched to Patient/2 and you execute the search:
589         * Observation?subject:mdm=Patient/1 , you will receive observations for both Patient/1 and Patient/2.
590         * <p>
591         * Default is <code>false</code>
592         * </p>
593         *
594         * @since 5.4.0
595         */
596        public void setAllowMdmExpansion(boolean theAllowMdmExpansion) {
597                myAllowMdmExpansion = theAllowMdmExpansion;
598        }
599
600        /**
601         * If set to <code>true</code> (default is <code>false</code>) the server will allow
602         * resources to have references to external servers. For example if this server is
603         * running at <code>http://example.com/fhir</code> and this setting is set to
604         * <code>true</code> the server will allow a Patient resource to be saved with a
605         * Patient.organization value of <code>http://foo.com/Organization/1</code>.
606         * <p>
607         * Under the default behaviour if this value has not been changed, the above
608         * resource would be rejected by the server because it requires all references
609         * to be resolvable on the local server.
610         * </p>
611         * <p>
612         * Note that external references will be indexed by the server and may be searched
613         * (e.g. <code>Patient:organization</code>), but
614         * chained searches (e.g. <code>Patient:organization.name</code>) will not work across
615         * these references.
616         * </p>
617         * <p>
618         * It is recommended to also set {@link #setTreatBaseUrlsAsLocal(Set)} if this value
619         * is set to <code>true</code>
620         * </p>
621         *
622         * @see #setTreatBaseUrlsAsLocal(Set)
623         * @see #setAllowExternalReferences(boolean)
624         */
625        public boolean isAllowExternalReferences() {
626                return myAllowExternalReferences;
627        }
628
629        /**
630         * If set to <code>true</code> (default is <code>false</code>) the server will allow
631         * resources to have references to external servers. For example if this server is
632         * running at <code>http://example.com/fhir</code> and this setting is set to
633         * <code>true</code> the server will allow a Patient resource to be saved with a
634         * Patient.organization value of <code>http://foo.com/Organization/1</code>.
635         * <p>
636         * Under the default behaviour if this value has not been changed, the above
637         * resource would be rejected by the server because it requires all references
638         * to be resolvable on the local server.
639         * </p>
640         * <p>
641         * Note that external references will be indexed by the server and may be searched
642         * (e.g. <code>Patient:organization</code>), but
643         * chained searches (e.g. <code>Patient:organization.name</code>) will not work across
644         * these references.
645         * </p>
646         * <p>
647         * It is recommended to also set {@link #setTreatBaseUrlsAsLocal(Set)} if this value
648         * is set to <code>true</code>
649         * </p>
650         *
651         * @see #setTreatBaseUrlsAsLocal(Set)
652         * @see #setAllowExternalReferences(boolean)
653         */
654        public void setAllowExternalReferences(boolean theAllowExternalReferences) {
655                myAllowExternalReferences = theAllowExternalReferences;
656        }
657
658        /**
659         * This setting may be used to advise the server that any references found in
660         * resources that have any of the base URLs given here will be replaced with
661         * simple local references.
662         * <p>
663         * For example, if the set contains the value <code>http://example.com/base/</code>
664         * and a resource is submitted to the server that contains a reference to
665         * <code>http://example.com/base/Patient/1</code>, the server will automatically
666         * convert this reference to <code>Patient/1</code>
667         * </p>
668         * <p>
669         * Note that this property has different behaviour from {@link StorageSettings#getTreatReferencesAsLogical()}
670         * </p>
671         *
672         * @see #getTreatReferencesAsLogical()
673         */
674        public Set<String> getTreatBaseUrlsAsLocal() {
675                return myTreatBaseUrlsAsLocal;
676        }
677
678        /**
679         * This setting may be used to advise the server that any references found in
680         * resources that have any of the base URLs given here will be replaced with
681         * simple local references.
682         * <p>
683         * For example, if the set contains the value <code>http://example.com/base/</code>
684         * and a resource is submitted to the server that contains a reference to
685         * <code>http://example.com/base/Patient/1</code>, the server will automatically
686         * convert this reference to <code>Patient/1</code>
687         * </p>
688         *
689         * @param theTreatBaseUrlsAsLocal The set of base URLs. May be <code>null</code>, which
690         *                                means no references will be treated as external
691         */
692        public void setTreatBaseUrlsAsLocal(Set<String> theTreatBaseUrlsAsLocal) {
693                if (theTreatBaseUrlsAsLocal != null) {
694                        for (String next : theTreatBaseUrlsAsLocal) {
695                                validateTreatBaseUrlsAsLocal(next);
696                        }
697                }
698
699                HashSet<String> treatBaseUrlsAsLocal = new HashSet<>();
700                for (String next : defaultIfNull(theTreatBaseUrlsAsLocal, new HashSet<String>())) {
701                        while (next.endsWith("/")) {
702                                next = next.substring(0, next.length() - 1);
703                        }
704                        treatBaseUrlsAsLocal.add(next);
705                }
706                myTreatBaseUrlsAsLocal = treatBaseUrlsAsLocal;
707        }
708
709        /**
710         * Add a value to the {@link #setTreatReferencesAsLogical(Set) logical references list}.
711         *
712         * @see #setTreatReferencesAsLogical(Set)
713         */
714        public void addTreatReferencesAsLogical(String theTreatReferencesAsLogical) {
715                validateTreatBaseUrlsAsLocal(theTreatReferencesAsLogical);
716
717                if (myTreatReferencesAsLogical == null) {
718                        myTreatReferencesAsLogical = new HashSet<>();
719                }
720                myTreatReferencesAsLogical.add(theTreatReferencesAsLogical);
721        }
722
723        /**
724         * This setting may be used to advise the server that any references found in
725         * resources that have any of the base URLs given here will be treated as logical
726         * references instead of being treated as real references.
727         * <p>
728         * A logical reference is a reference which is treated as an identifier, and
729         * does not neccesarily resolve. See <a href="http://hl7.org/fhir/references.html">references</a> for
730         * a description of logical references. For example, the valueset
731         * <a href="http://hl7.org/fhir/valueset-quantity-comparator.html">valueset-quantity-comparator</a> is a logical
732         * reference.
733         * </p>
734         * <p>
735         * Values for this field may take either of the following forms:
736         * </p>
737         * <ul>
738         * <li><code>http://example.com/some-url</code> <b>(will be matched exactly)</b></li>
739         * <li><code>http://example.com/some-base*</code> <b>(will match anything beginning with the part before the *)</b></li>
740         * </ul>
741         *
742         * @see #DEFAULT_LOGICAL_BASE_URLS Default values for this property
743         */
744        public Set<String> getTreatReferencesAsLogical() {
745                return myTreatReferencesAsLogical;
746        }
747
748        /**
749         * This setting may be used to advise the server that any references found in
750         * resources that have any of the base URLs given here will be treated as logical
751         * references instead of being treated as real references.
752         * <p>
753         * A logical reference is a reference which is treated as an identifier, and
754         * does not neccesarily resolve. See <a href="http://hl7.org/fhir/references.html">references</a> for
755         * a description of logical references. For example, the valueset
756         * <a href="http://hl7.org/fhir/valueset-quantity-comparator.html">valueset-quantity-comparator</a> is a logical
757         * reference.
758         * </p>
759         * <p>
760         * Values for this field may take either of the following forms:
761         * </p>
762         * <ul>
763         * <li><code>http://example.com/some-url</code> <b>(will be matched exactly)</b></li>
764         * <li><code>http://example.com/some-base*</code> <b>(will match anything beginning with the part before the *)</b></li>
765         * </ul>
766         *
767         * @see #DEFAULT_LOGICAL_BASE_URLS Default values for this property
768         */
769        public StorageSettings setTreatReferencesAsLogical(Set<String> theTreatReferencesAsLogical) {
770                myTreatReferencesAsLogical = theTreatReferencesAsLogical;
771                return this;
772        }
773
774        /**
775         * This setting indicates which subscription channel types are supported by the server.  Any subscriptions submitted
776         * to the server matching these types will be activated.
777         */
778        public StorageSettings addSupportedSubscriptionType(
779                        Subscription.SubscriptionChannelType theSubscriptionChannelType) {
780                mySupportedSubscriptionTypes.add(theSubscriptionChannelType);
781                return this;
782        }
783
784        /**
785         * This setting indicates which subscription channel types are supported by the server.  Any subscriptions submitted
786         * to the server matching these types will be activated.
787         */
788        public Set<Subscription.SubscriptionChannelType> getSupportedSubscriptionTypes() {
789                return Collections.unmodifiableSet(mySupportedSubscriptionTypes);
790        }
791
792        /**
793         * Indicate whether a subscription channel type is supported by this server.
794         *
795         * @return true if at least one subscription channel type is supported by this server false otherwise.
796         */
797        public boolean hasSupportedSubscriptionTypes() {
798                return CollectionUtils.isNotEmpty(mySupportedSubscriptionTypes);
799        }
800
801        @VisibleForTesting
802        public void clearSupportedSubscriptionTypesForUnitTest() {
803                mySupportedSubscriptionTypes.clear();
804        }
805
806        /**
807         * If e-mail subscriptions are supported, the From address used when sending e-mails
808         */
809        public String getEmailFromAddress() {
810                return myEmailFromAddress;
811        }
812
813        /**
814         * If e-mail subscriptions are supported, the From address used when sending e-mails
815         */
816        public void setEmailFromAddress(String theEmailFromAddress) {
817                myEmailFromAddress = theEmailFromAddress;
818        }
819
820        /**
821         * If websocket subscriptions are enabled, this specifies the context path that listens to them.  Default value "/websocket".
822         */
823        public String getWebsocketContextPath() {
824                return myWebsocketContextPath;
825        }
826
827        /**
828         * If websocket subscriptions are enabled, this specifies the context path that listens to them.  Default value "/websocket".
829         */
830        public void setWebsocketContextPath(String theWebsocketContextPath) {
831                myWebsocketContextPath = theWebsocketContextPath;
832        }
833
834        /**
835         * <p>
836         * Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
837         * {@link ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
838         * precision of {@link TemporalPrecisionEnum#DAY}.
839         * <p>
840         * For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
841         * integer representing the ordinal date {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
842         * and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
843         * </p>
844         * Default is {@literal true} beginning in HAPI FHIR 5.0.0
845         * </p>
846         *
847         * @since 5.0.0
848         */
849        public boolean getUseOrdinalDatesForDayPrecisionSearches() {
850                return myUseOrdinalDatesForDayPrecisionSearches;
851        }
852
853        /**
854         * <p>
855         * Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
856         * {@link ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
857         * precision of {@link TemporalPrecisionEnum#DAY}.
858         * <p>
859         * For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
860         * ordinal {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
861         * and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
862         * </p>
863         * Default is {@literal true} beginning in HAPI FHIR 5.0.0
864         * </p>
865         *
866         * @since 5.0.0
867         */
868        public void setUseOrdinalDatesForDayPrecisionSearches(boolean theUseOrdinalDates) {
869                myUseOrdinalDatesForDayPrecisionSearches = theUseOrdinalDates;
870        }
871
872        /**
873         * If set to <code>true</code> (default is <code>false</code>), when indexing SearchParameter values for token SearchParameter,
874         * the string component to support the <code>:text</code> modifier will be disabled. This means that the following fields
875         * will not be indexed for tokens:
876         * <ul>
877         *    <li>CodeableConcept.text</li>
878         *    <li>Coding.display</li>
879         *    <li>Identifier.use.text</li>
880         * </ul>
881         *
882         * @since 5.0.0
883         */
884        public boolean isSuppressStringIndexingInTokens() {
885                return mySuppressStringIndexingInTokens;
886        }
887
888        /**
889         * If set to <code>true</code> (default is <code>false</code>), when indexing SearchParameter values for token SearchParameter,
890         * the string component to support the <code>:text</code> modifier will be disabled. This means that the following fields
891         * will not be indexed for tokens:
892         * <ul>
893         *    <li>CodeableConcept.text</li>
894         *    <li>Coding.display</li>
895         *    <li>Identifier.use.text</li>
896         * </ul>
897         *
898         * @since 5.0.0
899         */
900        public void setSuppressStringIndexingInTokens(boolean theSuppressStringIndexingInTokens) {
901                mySuppressStringIndexingInTokens = theSuppressStringIndexingInTokens;
902        }
903
904        /**
905         * When indexing a Period (e.g. Encounter.period) where the period has an upper bound
906         * but not a lower bound, a canned "start of time" value can be used as the lower bound
907         * in order to allow range searches to correctly identify all values in the range.
908         * <p>
909         * The default value for this is {@link #DEFAULT_PERIOD_INDEX_START_OF_TIME} which
910         * is probably good enough for almost any application, but this can be changed if
911         * needed.
912         * </p>
913         * <p>
914         * Note the following database documented limitations:
915         *    <ul>
916         *       <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
917         *       <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>
918         *       <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
919         *       <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
920         *       <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
921         *     </ul>
922         * </p>
923         *
924         * @see #getPeriodIndexEndOfTime()
925         * @since 5.1.0
926         */
927        public IPrimitiveType<Date> getPeriodIndexStartOfTime() {
928                return myPeriodIndexStartOfTime;
929        }
930
931        /**
932         * When indexing a Period (e.g. Encounter.period) where the period has an upper bound
933         * but not a lower bound, a canned "start of time" value can be used as the lower bound
934         * in order to allow range searches to correctly identify all values in the range.
935         * <p>
936         * The default value for this is {@link #DEFAULT_PERIOD_INDEX_START_OF_TIME} which
937         * is probably good enough for almost any application, but this can be changed if
938         * needed.
939         * </p>
940         * <p>
941         * Note the following database documented limitations:
942         *    <ul>
943         *       <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
944         *       <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>
945         *       <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
946         *       <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
947         *       <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
948         *     </ul>
949         * </p>
950         *
951         * @see #getPeriodIndexEndOfTime()
952         * @since 5.1.0
953         */
954        public void setPeriodIndexStartOfTime(IPrimitiveType<Date> thePeriodIndexStartOfTime) {
955                Validate.notNull(thePeriodIndexStartOfTime, "thePeriodIndexStartOfTime must not be null");
956                myPeriodIndexStartOfTime = thePeriodIndexStartOfTime;
957        }
958
959        /**
960         * When indexing a Period (e.g. Encounter.period) where the period has a lower bound
961         * but not an upper bound, a canned "end of time" value can be used as the upper bound
962         * in order to allow range searches to correctly identify all values in the range.
963         * <p>
964         * The default value for this is {@link #DEFAULT_PERIOD_INDEX_START_OF_TIME} which
965         * is probably good enough for almost any application, but this can be changed if
966         * needed.
967         * </p>
968         * <p>
969         * Note the following database documented limitations:
970         *    <ul>
971         *       <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
972         *       <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>
973         *       <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
974         *       <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
975         *       <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
976         *     </ul>
977         * </p>
978         *
979         * @see #getPeriodIndexStartOfTime()
980         * @since 5.1.0
981         */
982        public IPrimitiveType<Date> getPeriodIndexEndOfTime() {
983                return myPeriodIndexEndOfTime;
984        }
985
986        /**
987         * When indexing a Period (e.g. Encounter.period) where the period has an upper bound
988         * but not a lower bound, a canned "start of time" value can be used as the lower bound
989         * in order to allow range searches to correctly identify all values in the range.
990         * <p>
991         * The default value for this is {@link #DEFAULT_PERIOD_INDEX_START_OF_TIME} which
992         * is probably good enough for almost any application, but this can be changed if
993         * needed.
994         * </p>
995         * <p>
996         * Note the following database documented limitations:
997         *    <ul>
998         *       <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
999         *       <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>
1000         *       <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
1001         *       <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
1002         *       <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
1003         *     </ul>
1004         * </p>
1005         *
1006         * @see #getPeriodIndexStartOfTime()
1007         * @since 5.1.0
1008         */
1009        public void setPeriodIndexEndOfTime(IPrimitiveType<Date> thePeriodIndexEndOfTime) {
1010                Validate.notNull(thePeriodIndexEndOfTime, "thePeriodIndexEndOfTime must not be null");
1011                myPeriodIndexEndOfTime = thePeriodIndexEndOfTime;
1012        }
1013
1014        /**
1015         * Toggles whether Quantity searches support value normalization when using valid UCUM coded values.
1016         *
1017         * <p>
1018         * The default value is {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED} which is current behavior.
1019         * </p>
1020         * <p>
1021         * Here is the UCUM service support level
1022         *    <ul>
1023         *       <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED}, default, Quantity is stored in {@link ResourceIndexedSearchParamQuantity} only and it is used by searching.</li>
1024         *       <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>
1025         *       <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching.</li>
1026         *     </ul>
1027         * </p>
1028         *
1029         * @since 5.3.0
1030         */
1031        public NormalizedQuantitySearchLevel getNormalizedQuantitySearchLevel() {
1032                return myNormalizedQuantitySearchLevel;
1033        }
1034
1035        /**
1036         * Toggles whether Quantity searches support value normalization when using valid UCUM coded values.
1037         *
1038         * <p>
1039         * The default value is {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED} which is current behavior.
1040         * </p>
1041         * <p>
1042         * Here is the UCUM service support level
1043         *    <ul>
1044         *       <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED}, default, Quantity is stored in {@link ResourceIndexedSearchParamQuantity} only and it is used by searching.</li>
1045         *       <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>
1046         *       <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching.</li>
1047         *     </ul>
1048         * </p>
1049         *
1050         * @since 5.3.0
1051         */
1052        public void setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel theNormalizedQuantitySearchLevel) {
1053                myNormalizedQuantitySearchLevel = theNormalizedQuantitySearchLevel;
1054        }
1055
1056        /**
1057         * When set with resource paths (e.g. <code>"Observation.subject"</code>), any references found at the given paths
1058         * will automatically have versions appended. The version used will be the current version of the given resource.
1059         *
1060         * @since 5.3.0
1061         */
1062        public Set<String> getAutoVersionReferenceAtPaths() {
1063                return myAutoVersionReferenceAtPaths;
1064        }
1065
1066        /**
1067         * When set with resource paths (e.g. <code>"Observation.subject"</code>), any references found at the given paths
1068         * will automatically have versions appended. The version used will be the current version of the given resource.
1069         * <p>
1070         * Versions will only be added if the reference does not already have a version, so any versioned references
1071         * supplied by the client will take precedence over the automatic current version.
1072         * </p>
1073         * <p>
1074         * Note that for this setting to be useful, the {@link ParserOptions}
1075         * {@link ParserOptions#getDontStripVersionsFromReferencesAtPaths() DontStripVersionsFromReferencesAtPaths}
1076         * option must also be set.
1077         * </p>
1078         *
1079         * @param thePaths A collection of reference paths for which the versions will be appended automatically
1080         *                 when serializing, e.g. "Patient.managingOrganization" or "AuditEvent.object.reference". Note that
1081         *                 only resource name and field names with dots separating is allowed here (no repetition
1082         *                 indicators, FluentPath expressions, etc.)
1083         * @since 5.3.0
1084         */
1085        public void setAutoVersionReferenceAtPaths(String... thePaths) {
1086                Set<String> paths = Collections.emptySet();
1087                if (thePaths != null) {
1088                        paths = new HashSet<>(Arrays.asList(thePaths));
1089                }
1090                setAutoVersionReferenceAtPaths(paths);
1091        }
1092
1093        /**
1094         * When set with resource paths (e.g. <code>"Observation.subject"</code>), any references found at the given paths
1095         * will automatically have versions appended. The version used will be the current version of the given resource.
1096         * <p>
1097         * Versions will only be added if the reference does not already have a version, so any versioned references
1098         * supplied by the client will take precedence over the automatic current version.
1099         * </p>
1100         * <p>
1101         * Note that for this setting to be useful, the {@link ParserOptions}
1102         * {@link ParserOptions#getDontStripVersionsFromReferencesAtPaths() DontStripVersionsFromReferencesAtPaths}
1103         * option must also be set
1104         * </p>
1105         *
1106         * @param thePaths A collection of reference paths for which the versions will be appended automatically
1107         *                 when serializing, e.g. "Patient.managingOrganization" or "AuditEvent.object.reference". Note that
1108         *                 only resource name and field names with dots separating is allowed here (no repetition
1109         *                 indicators, FluentPath expressions, etc.)
1110         * @since 5.3.0
1111         */
1112        public void setAutoVersionReferenceAtPaths(Set<String> thePaths) {
1113                Set<String> paths = defaultIfNull(thePaths, Collections.emptySet());
1114                Map<String, Set<String>> byType = new HashMap<>();
1115                for (String nextPath : paths) {
1116                        int doxIdx = nextPath.indexOf('.');
1117                        Validate.isTrue(doxIdx > 0, "Invalid path for auto-version reference at path: %s", nextPath);
1118                        String type = nextPath.substring(0, doxIdx);
1119                        byType.computeIfAbsent(type, t -> new HashSet<>()).add(nextPath);
1120                }
1121
1122                myAutoVersionReferenceAtPaths = paths;
1123                myTypeToAutoVersionReferenceAtPaths = byType;
1124        }
1125
1126        /**
1127         * Returns a sub-collection of {@link #getAutoVersionReferenceAtPaths()} containing only paths
1128         * for the given resource type.
1129         *
1130         * @since 5.3.0
1131         */
1132        public Set<String> getAutoVersionReferenceAtPathsByResourceType(String theResourceType) {
1133                Validate.notEmpty(theResourceType, "theResourceType must not be null or empty");
1134                Set<String> retVal = myTypeToAutoVersionReferenceAtPaths.get(theResourceType);
1135                retVal = defaultIfNull(retVal, Collections.emptySet());
1136                return retVal;
1137        }
1138
1139        /**
1140         * Should searches with <code>_include</code> respect versioned references, and pull the specific requested version.
1141         * This may have performance impacts on heavily loaded systems.
1142         *
1143         * @since 5.3.0
1144         */
1145        public boolean isRespectVersionsForSearchIncludes() {
1146                return myRespectVersionsForSearchIncludes;
1147        }
1148
1149        /**
1150         * Should searches with <code>_include</code> respect versioned references, and pull the specific requested version.
1151         * This may have performance impacts on heavily loaded systems.
1152         *
1153         * @since 5.3.0
1154         */
1155        public void setRespectVersionsForSearchIncludes(boolean theRespectVersionsForSearchIncludes) {
1156                myRespectVersionsForSearchIncludes = theRespectVersionsForSearchIncludes;
1157        }
1158
1159        /**
1160         * If enabled, "Uplifted Refchains" will be enabled. This feature causes
1161         * HAPI FHIR to generate indexes for stored resources that include the current
1162         * value of the target of a chained reference, such as "Encounter?subject.name".
1163         *
1164         * @since 6.6.0
1165         */
1166        public boolean isIndexOnUpliftedRefchains() {
1167                return myIndexOnUpliftedRefchains;
1168        }
1169
1170        /**
1171         * If enabled, "Uplifted Refchains" will be enabled. This feature causes
1172         * HAPI FHIR to generate indexes for stored resources that include the current
1173         * value of the target of a chained reference, such as "Encounter?subject.name".
1174         *
1175         * @since 6.6.0
1176         */
1177        public void setIndexOnUpliftedRefchains(boolean theIndexOnUpliftedRefchains) {
1178                myIndexOnUpliftedRefchains = theIndexOnUpliftedRefchains;
1179        }
1180
1181        /**
1182         * Should indexing and searching on contained resources be enabled on this server.
1183         * This may have performance impacts, and should be enabled only if it is needed. Default is <code>false</code>.
1184         *
1185         * @since 5.4.0
1186         */
1187        public boolean isIndexOnContainedResources() {
1188                return myIndexOnContainedResources;
1189        }
1190
1191        /**
1192         * Should indexing and searching on contained resources be enabled on this server.
1193         * This may have performance impacts, and should be enabled only if it is needed. Default is <code>false</code>.
1194         *
1195         * @since 5.4.0
1196         */
1197        public void setIndexOnContainedResources(boolean theIndexOnContainedResources) {
1198                myIndexOnContainedResources = theIndexOnContainedResources;
1199        }
1200
1201        /**
1202         * Should recursive indexing and searching on contained resources be enabled on this server.
1203         * This may have performance impacts, and should be enabled only if it is needed. Default is <code>false</code>.
1204         *
1205         * @since 5.6.0
1206         */
1207        public boolean isIndexOnContainedResourcesRecursively() {
1208                return myIndexOnContainedResourcesRecursively;
1209        }
1210
1211        /**
1212         * Should recursive indexing and searching on contained resources be enabled on this server.
1213         * This may have performance impacts, and should be enabled only if it is needed. Default is <code>false</code>.
1214         *
1215         * @since 5.6.0
1216         */
1217        public void setIndexOnContainedResourcesRecursively(boolean theIndexOnContainedResourcesRecursively) {
1218                myIndexOnContainedResourcesRecursively = theIndexOnContainedResourcesRecursively;
1219        }
1220
1221        /**
1222         * If this is disabled by setting this to {@literal false} (default is {@literal true}),
1223         * the server will not automatically implement and support search parameters that
1224         * are not explcitly created in the repository.
1225         * <p>
1226         * Disabling this can have a dramatic improvement on performance (especially write performance)
1227         * in servers that only need to support a small number of search parameters, or no search parameters at all.
1228         * Disabling this obviously reduces the options for searching however.
1229         * </p>
1230         *
1231         * @since 5.7.0
1232         */
1233        public boolean isAutoSupportDefaultSearchParams() {
1234                return myAutoSupportDefaultSearchParams;
1235        }
1236
1237        /**
1238         * If this is disabled by setting this to {@literal false} (default is {@literal true}),
1239         * the server will not automatically implement and support search parameters that
1240         * are not explcitly created in the repository.
1241         * <p>
1242         * Disabling this can have a dramatic improvement on performance (especially write performance)
1243         * in servers that only need to support a small number of search parameters, or no search parameters at all.
1244         * Disabling this obviously reduces the options for searching however.
1245         * </p>
1246         *
1247         * @since 5.7.0
1248         */
1249        public void setAutoSupportDefaultSearchParams(boolean theAutoSupportDefaultSearchParams) {
1250                myAutoSupportDefaultSearchParams = theAutoSupportDefaultSearchParams;
1251        }
1252
1253        /**
1254         * If enabled, the server will support cross-partition subscription.
1255         * This subscription will be the responsible for all the requests from all the partitions on this server.
1256         * For example, if the server has 3 partitions, P1, P2, P3
1257         * The subscription will live in the DEFAULT partition. Resource posted to DEFAULT, P1, P2, and P3 will trigger this subscription.
1258         * <p>
1259         * Default is <code>false</code>
1260         * </p>
1261         *
1262         * @since 5.7.0
1263         */
1264        public boolean isCrossPartitionSubscriptionEnabled() {
1265                return myCrossPartitionSubscriptionEnabled;
1266        }
1267
1268        /**
1269         * If enabled, the server will support cross-partition subscription.
1270         * This subscription will be the responsible for all the requests from all the partitions on this server.
1271         * For example, if the server has 3 partitions, P1, P2, P3
1272         * The subscription will live in the DEFAULT partition. Resource posted to DEFAULT, P1, P2, and P3 will trigger this subscription.
1273         * <p>
1274         * Default is <code>false</code>
1275         * </p>
1276         *
1277         * @since 5.7.0
1278         */
1279        public void setCrossPartitionSubscriptionEnabled(boolean theAllowCrossPartitionSubscription) {
1280                myCrossPartitionSubscriptionEnabled = theAllowCrossPartitionSubscription;
1281        }
1282
1283        /**
1284         * This setting controls whether the {@link  BaseChannelSettings#isQualifyChannelName}
1285         * should be qualified or not.
1286         * Default is true, ie, the channel name will be qualified.
1287         *
1288         * @since 6.4.0
1289         */
1290        public void setQualifySubscriptionMatchingChannelName(boolean theQualifySubscriptionMatchingChannelName) {
1291                myQualifySubscriptionMatchingChannelName = theQualifySubscriptionMatchingChannelName;
1292        }
1293
1294        /**
1295         * This setting return whether the {@link BaseChannelSettings#isQualifyChannelName}
1296         * should be qualified or not.
1297         *
1298         * @return whether the {@link BaseChannelSettings#isQualifyChannelName} is qualified or not
1299         * @since 6.4.0
1300         */
1301        public boolean isQualifySubscriptionMatchingChannelName() {
1302                return myQualifySubscriptionMatchingChannelName;
1303        }
1304
1305        /**
1306         * @return Should the {@literal _lamguage} SearchParameter be supported on this server? Defaults to {@literal false}.
1307         * @since 7.0.0
1308         */
1309        public boolean isLanguageSearchParameterEnabled() {
1310                return myLanguageSearchParameterEnabled;
1311        }
1312
1313        /**
1314         * Should the {@literal _lamguage} SearchParameter be supported on this server? Defaults to {@literal false}.
1315         *
1316         * @since 7.0.0
1317         */
1318        public void setLanguageSearchParameterEnabled(boolean theLanguageSearchParameterEnabled) {
1319                myLanguageSearchParameterEnabled = theLanguageSearchParameterEnabled;
1320        }
1321
1322        private static void validateTreatBaseUrlsAsLocal(String theUrl) {
1323                Validate.notBlank(theUrl, "Base URL must not be null or empty");
1324
1325                int starIdx = theUrl.indexOf('*');
1326                if (starIdx != -1) {
1327                        if (starIdx != theUrl.length() - 1) {
1328                                throw new IllegalArgumentException(Msg.code(1525)
1329                                                + "Base URL wildcard character (*) can only appear at the end of the string: " + theUrl);
1330                        }
1331                }
1332        }
1333
1334        public enum IndexEnabledEnum {
1335                ENABLED,
1336                DISABLED
1337        }
1338}