
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}