package ca.uhn.fhir.jpa.dao; import java.util.*; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; /* * #%L * HAPI FHIR JPA Server * %% * Copyright (C) 2014 - 2017 University Health Network * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; public class DaoConfig { /** * Default {@link #getTreatReferencesAsLogical() logical URL bases}. Includes the following * values: * <ul> * <li><code>"http://hl7.org/fhir/valueset-*"</code></li> * <li><code>"http://hl7.org/fhir/codesystem-*"</code></li> * <li><code>"http://hl7.org/fhir/StructureDefinition/*"</code></li> * </ul> */ public static final Set<String> DEFAULT_LOGICAL_BASE_URLS = Collections.unmodifiableSet(new HashSet<String>(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/*"))); /** * Default value for {@link #setMaximumSearchResultCountInTransaction(int)} * * @see #setMaximumSearchResultCountInTransaction(int) */ private static final int DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION = 500; /** * Default value for {@link #setReuseCachedSearchResultsForMillis(Long)}: 60000ms (one minute) */ public static final Long DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS = DateUtils.MILLIS_PER_MINUTE; // *** // update setter javadoc if default changes // *** private boolean myAllowExternalReferences = false; // *** // update setter javadoc if default changes // *** private boolean myAllowInlineMatchUrlReferences = true; private boolean myAllowMultipleDelete; private boolean myDefaultSearchParamsCanBeOverridden = false; // *** // update setter javadoc if default changes // *** private int myDeferIndexingForCodesystemsOfSize = 2000; private boolean myDeleteStaleSearches = true; // *** // update setter javadoc if default changes // *** private long myExpireSearchResultsAfterMillis = DateUtils.MILLIS_PER_HOUR; private int myHardTagListLimit = 1000; private int myIncludeLimit = 2000; // *** // update setter javadoc if default changes // *** private boolean myIndexContainedResources = true; private List<IServerInterceptor> myInterceptors; // *** // update setter javadoc if default changes // *** private int myMaximumExpansionSize = 5000; private int myMaximumSearchResultCountInTransaction = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION; private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC; private Long myReuseCachedSearchResultsForMillis = DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS; private boolean mySchedulingDisabled; private boolean mySubscriptionEnabled; private long mySubscriptionPollDelay = 1000; private Long mySubscriptionPurgeInactiveAfterMillis; private boolean mySuppressUpdatesWithNoChange = true; private Set<String> myTreatBaseUrlsAsLocal = new HashSet<String>(); private Set<String> myTreatReferencesAsLogical = new HashSet<String>(DEFAULT_LOGICAL_BASE_URLS); /** * Add a value to the {@link #setTreatReferencesAsLogical(Set) logical references list}. * * @see #setTreatReferencesAsLogical(Set) */ public void addTreatReferencesAsLogical(String theTreatReferencesAsLogical) { validateTreatBaseUrlsAsLocal(theTreatReferencesAsLogical); if (myTreatReferencesAsLogical == null) { myTreatReferencesAsLogical = new HashSet<String>(); } myTreatReferencesAsLogical.add(theTreatReferencesAsLogical); } /** * When a code system is added that contains more than this number of codes, * the code system will be indexed later in an incremental process in order to * avoid overwhelming Lucene with a huge number of codes in a single operation. * <p> * Defaults to 2000 * </p> */ public int getDeferIndexingForCodesystemsOfSize() { return myDeferIndexingForCodesystemsOfSize; } /** * Sets the number of milliseconds that search results for a given client search * should be preserved before being purged from the database. * <p> * Search results are stored in the database so that they can be paged over multiple * requests. After this * number of milliseconds, they will be deleted from the database, and any paging links * (next/prev links in search response bundles) will become invalid. Defaults to 1 hour. * </p> * <p> * <p> * To disable this feature entirely, see {@link #setExpireSearchResults(boolean)} * </p> * * @since 1.5 */ public long getExpireSearchResultsAfterMillis() { return myExpireSearchResultsAfterMillis; } /** * Gets the maximum number of results to return in a GetTags query (DSTU1 only) */ public int getHardTagListLimit() { return myHardTagListLimit; } public int getIncludeLimit() { return myIncludeLimit; } /** * Returns the interceptors which will be notified of operations. * * @see #setInterceptors(List) */ public List<IServerInterceptor> getInterceptors() { if (myInterceptors == null) { return myInterceptors = new ArrayList<IServerInterceptor>(); } return myInterceptors; } /** * See {@link #setMaximumExpansionSize(int)} */ public int getMaximumExpansionSize() { return myMaximumExpansionSize; } /** * Provides the maximum number of results which may be returned by a search within a FHIR <code>transaction</code> * operation. For example, if this value is set to <code>100</code> and a FHIR transaction is processed with a sub-request * for <code>Patient?gender=male</code>, the server will throw an error (and the transaction will fail) if there are more than * 100 resources on the server which match this query. * * @see #DEFAULT_LOGICAL_BASE_URLS The default value for this setting */ public int getMaximumSearchResultCountInTransaction() { return myMaximumSearchResultCountInTransaction; } public ResourceEncodingEnum getResourceEncoding() { return myResourceEncoding; } /** * If set to a non {@literal null} value (default is {@link #DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS non null}) * if an identical search is requested multiple times within this window, the same results will be returned * to multiple queries. For example, if this value is set to 1 minute and a client searches for all * patients named "smith", and then a second client also performs the same search within 1 minute, * the same cached results will be returned. * <p> * This approach can improve performance, especially under heavy load, but can also mean that * searches may potentially return slightly out-of-date results. * </p> */ public Long getReuseCachedSearchResultsForMillis() { return myReuseCachedSearchResultsForMillis; } public long getSubscriptionPollDelay() { return mySubscriptionPollDelay; } public Long getSubscriptionPurgeInactiveAfterMillis() { return mySubscriptionPurgeInactiveAfterMillis; } /** * This setting may be used to advise the server that any references found in * resources that have any of the base URLs given here will be replaced with * simple local references. * <p> * For example, if the set contains the value <code>http://example.com/base/</code> * and a resource is submitted to the server that contains a reference to * <code>http://example.com/base/Patient/1</code>, the server will automatically * convert this reference to <code>Patient/1</code> * </p> * <p> * Note that this property has different behaviour from {@link DaoConfig#getTreatReferencesAsLogical()} * </p> * * @see #getTreatReferencesAsLogical() */ public Set<String> getTreatBaseUrlsAsLocal() { return myTreatBaseUrlsAsLocal; } /** * This setting may be used to advise the server that any references found in * resources that have any of the base URLs given here will be treated as logical * references instead of being treated as real references. * <p> * A logical reference is a reference which is treated as an identifier, and * does not neccesarily resolve. See {@link "http://hl7.org/fhir/references.html"} for * a description of logical references. For example, the valueset * {@link "http://hl7.org/fhir/valueset-quantity-comparator.html"} is a logical * reference. * </p> * <p> * Values for this field may take either of the following forms: * </p> * <ul> * <li><code>http://example.com/some-url</code> <b>(will be matched exactly)</b></li> * <li><code>http://example.com/some-base*</code> <b>(will match anything beginning with the part before the *)</b></li> * </ul> * * @see #DEFAULT_LOGICAL_BASE_URLS Default values for this property */ public Set<String> getTreatReferencesAsLogical() { return myTreatReferencesAsLogical; } /** * If set to <code>true</code> (default is <code>false</code>) the server will allow * resources to have references to external servers. For example if this server is * running at <code>http://example.com/fhir</code> and this setting is set to * <code>true</code> the server will allow a Patient resource to be saved with a * Patient.organization value of <code>http://foo.com/Organization/1</code>. * <p> * Under the default behaviour if this value has not been changed, the above * resource would be rejected by the server because it requires all references * to be resolvable on the local server. * </p> * <p> * Note that external references will be indexed by the server and may be searched * (e.g. <code>Patient:organization</code>), but * chained searches (e.g. <code>Patient:organization.name</code>) will not work across * these references. * </p> * <p> * It is recommended to also set {@link #setTreatBaseUrlsAsLocal(Set)} if this value * is set to <code>true</code> * </p> * * @see #setTreatBaseUrlsAsLocal(Set) * @see #setAllowExternalReferences(boolean) */ public boolean isAllowExternalReferences() { return myAllowExternalReferences; } /** * @see #setAllowInlineMatchUrlReferences(boolean) */ public boolean isAllowInlineMatchUrlReferences() { return myAllowInlineMatchUrlReferences; } public boolean isAllowMultipleDelete() { return myAllowMultipleDelete; } /** * If set to {@code true} the default search params (i.e. the search parameters that are * defined by the FHIR specification itself) may be overridden by uploading search * parameters to the server with the same code as the built-in search parameter. * <p> * This can be useful if you want to be able to disable or alter * the behaviour of the default search parameters. * </p> * <p> * The default value for this setting is {@code false} * </p> */ public boolean isDefaultSearchParamsCanBeOverridden() { return myDefaultSearchParamsCanBeOverridden; } /** * If this is set to <code>false</code> (default is <code>true</code>) the stale search deletion * task will be disabled (meaning that search results will be retained in the database indefinitely). USE WITH CAUTION. * <p> * This feature is useful if you want to define your own process for deleting these (e.g. because * you are running in a cluster) * </p> */ public boolean isExpireSearchResults() { return myDeleteStaleSearches; } /** * Should contained IDs be indexed the same way that non-contained IDs are (default is * <code>true</code>) */ public boolean isIndexContainedResources() { return myIndexContainedResources; } public boolean isSchedulingDisabled() { return mySchedulingDisabled; } /** * See {@link #setSubscriptionEnabled(boolean)} */ public boolean isSubscriptionEnabled() { return mySubscriptionEnabled; } /** * If set to {@literal true} (default is true), if a client performs an update which does not actually * result in any chance to a given resource (e.g. an update where the resource body matches the * existing resource body in the database) the operation will succeed but a new version (and corresponding history * entry) will not actually be created. The existing resource version will be returned to the client. * <p> * If set to {@literal false}, all updates will result in the creation of a new version * </p> */ public boolean isSuppressUpdatesWithNoChange() { return mySuppressUpdatesWithNoChange; } /** * If set to <code>true</code> (default is <code>false</code>) the server will allow * resources to have references to external servers. For example if this server is * running at <code>http://example.com/fhir</code> and this setting is set to * <code>true</code> the server will allow a Patient resource to be saved with a * Patient.organization value of <code>http://foo.com/Organization/1</code>. * <p> * Under the default behaviour if this value has not been changed, the above * resource would be rejected by the server because it requires all references * to be resolvable on the local server. * </p> * <p> * Note that external references will be indexed by the server and may be searched * (e.g. <code>Patient:organization</code>), but * chained searches (e.g. <code>Patient:organization.name</code>) will not work across * these references. * </p> * <p> * It is recommended to also set {@link #setTreatBaseUrlsAsLocal(Set)} if this value * is set to <code>true</code> * </p> * * @see #setTreatBaseUrlsAsLocal(Set) * @see #setAllowExternalReferences(boolean) */ public void setAllowExternalReferences(boolean theAllowExternalReferences) { myAllowExternalReferences = theAllowExternalReferences; } /** * Should references containing match URLs be resolved and replaced in create and update operations. For * example, if this property is set to true and a resource is created containing a reference * to "Patient?identifier=12345", this is reference match URL will be resolved and replaced according * to the usual match URL rules. * <p> * Default is {@literal true} beginning in HAPI FHIR 2.4, since this * feature is now specified in the FHIR specification. (Previously it * was an experimental/rpposed feature) * </p> * * @since 1.5 */ public void setAllowInlineMatchUrlReferences(boolean theAllowInlineMatchUrlReferences) { myAllowInlineMatchUrlReferences = theAllowInlineMatchUrlReferences; } public void setAllowMultipleDelete(boolean theAllowMultipleDelete) { myAllowMultipleDelete = theAllowMultipleDelete; } /** * If set to {@code true} the default search params (i.e. the search parameters that are * defined by the FHIR specification itself) may be overridden by uploading search * parameters to the server with the same code as the built-in search parameter. * <p> * This can be useful if you want to be able to disable or alter * the behaviour of the default search parameters. * </p> * <p> * The default value for this setting is {@code false} * </p> */ public void setDefaultSearchParamsCanBeOverridden(boolean theDefaultSearchParamsCanBeOverridden) { myDefaultSearchParamsCanBeOverridden = theDefaultSearchParamsCanBeOverridden; } /** * When a code system is added that contains more than this number of codes, * the code system will be indexed later in an incremental process in order to * avoid overwhelming Lucene with a huge number of codes in a single operation. * <p> * Defaults to 2000 * </p> */ public void setDeferIndexingForCodesystemsOfSize(int theDeferIndexingForCodesystemsOfSize) { myDeferIndexingForCodesystemsOfSize = theDeferIndexingForCodesystemsOfSize; } /** * If this is set to <code>false</code> (default is <code>true</code>) the stale search deletion * task will be disabled (meaning that search results will be retained in the database indefinitely). USE WITH CAUTION. * <p> * This feature is useful if you want to define your own process for deleting these (e.g. because * you are running in a cluster) * </p> */ public void setExpireSearchResults(boolean theDeleteStaleSearches) { myDeleteStaleSearches = theDeleteStaleSearches; } /** * Sets the number of milliseconds that search results for a given client search * should be preserved before being purged from the database. * <p> * Search results are stored in the database so that they can be paged over multiple * requests. After this * number of milliseconds, they will be deleted from the database, and any paging links * (next/prev links in search response bundles) will become invalid. Defaults to 1 hour. * </p> * <p> * * <p> * To disable this feature entirely, see {@link #setExpireSearchResults(boolean)} * </p> * * @since 1.5 */ public void setExpireSearchResultsAfterMillis(long theExpireSearchResultsAfterMillis) { myExpireSearchResultsAfterMillis = theExpireSearchResultsAfterMillis; } /** * Do not call this method, it exists only for legacy reasons. It * will be removed in a future version. Configure the page size on your * paging provider instead. * * @deprecated This method does not do anything. Configure the page size on your * paging provider instead. Deprecated in HAPI FHIR 2.3 (Jan 2017) */ @Deprecated public void setHardSearchLimit(int theHardSearchLimit) { // this method does nothing } /** * Gets the maximum number of results to return in a GetTags query (DSTU1 only) */ public void setHardTagListLimit(int theHardTagListLimit) { myHardTagListLimit = theHardTagListLimit; } /** * This is the maximum number of resources that will be added to a single page of returned resources. Because of * includes with wildcards and other possibilities it is possible for a client to make requests that include very * large amounts of data, so this hard limit can be imposed to prevent runaway requests. */ public void setIncludeLimit(int theIncludeLimit) { myIncludeLimit = theIncludeLimit; } /** * Should contained IDs be indexed the same way that non-contained IDs are (default is * <code>true</code>) */ public void setIndexContainedResources(boolean theIndexContainedResources) { myIndexContainedResources = theIndexContainedResources; } /** * This may be used to optionally register server interceptors directly against the DAOs. */ public void setInterceptors(IServerInterceptor... theInterceptor) { setInterceptors(new ArrayList<IServerInterceptor>()); if (theInterceptor != null && theInterceptor.length != 0) { getInterceptors().addAll(Arrays.asList(theInterceptor)); } } /** * This may be used to optionally register server interceptors directly against the DAOs. */ public void setInterceptors(List<IServerInterceptor> theInterceptors) { myInterceptors = theInterceptors; } /** * Sets the maximum number of codes that will be added to a valueset expansion before * the operation will be failed as too costly */ public void setMaximumExpansionSize(int theMaximumExpansionSize) { Validate.isTrue(theMaximumExpansionSize > 0, "theMaximumExpansionSize must be > 0"); myMaximumExpansionSize = theMaximumExpansionSize; } /** * Provides the maximum number of results which may be returned by a search within a FHIR <code>transaction</code> * operation. For example, if this value is set to <code>100</code> and a FHIR transaction is processed with a sub-request * for <code>Patient?gender=male</code>, the server will throw an error (and the transaction will fail) if there are more than * 100 resources on the server which match this query. * * @see #DEFAULT_LOGICAL_BASE_URLS The default value for this setting */ public void setMaximumSearchResultCountInTransaction(int theMaximumSearchResultCountInTransaction) { myMaximumSearchResultCountInTransaction = theMaximumSearchResultCountInTransaction; } public void setResourceEncoding(ResourceEncodingEnum theResourceEncoding) { myResourceEncoding = theResourceEncoding; } /** * If set to a non {@literal null} value (default is {@link #DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS non null}) * if an identical search is requested multiple times within this window, the same results will be returned * to multiple queries. For example, if this value is set to 1 minute and a client searches for all * patients named "smith", and then a second client also performs the same search within 1 minute, * the same cached results will be returned. * <p> * This approach can improve performance, especially under heavy load, but can also mean that * searches may potentially return slightly out-of-date results. * </p> */ public void setReuseCachedSearchResultsForMillis(Long theReuseCachedSearchResultsForMillis) { myReuseCachedSearchResultsForMillis = theReuseCachedSearchResultsForMillis; } public void setSchedulingDisabled(boolean theSchedulingDisabled) { mySchedulingDisabled = theSchedulingDisabled; } /** * If set to true, the server will enable support for subscriptions. Subscriptions * will by default be handled via a polling task. Note that if this is enabled, you must also include Spring task scanning to your XML * config for the scheduled tasks used by the subscription module. */ public void setSubscriptionEnabled(boolean theSubscriptionEnabled) { mySubscriptionEnabled = theSubscriptionEnabled; } public void setSubscriptionPollDelay(long theSubscriptionPollDelay) { mySubscriptionPollDelay = theSubscriptionPollDelay; } public void setSubscriptionPurgeInactiveAfterMillis(Long theMillis) { if (theMillis != null) { Validate.exclusiveBetween(0, Long.MAX_VALUE, theMillis); } mySubscriptionPurgeInactiveAfterMillis = theMillis; } public void setSubscriptionPurgeInactiveAfterSeconds(int theSeconds) { setSubscriptionPurgeInactiveAfterMillis(theSeconds * DateUtils.MILLIS_PER_SECOND); } /** * If set to {@literal true} (default is true), if a client performs an update which does not actually * result in any chance to a given resource (e.g. an update where the resource body matches the * existing resource body in the database) the operation will succeed but a new version (and corresponding history * entry) will not actually be created. The existing resource version will be returned to the client. * <p> * If set to {@literal false}, all updates will result in the creation of a new version * </p> */ public void setSuppressUpdatesWithNoChange(boolean theSuppressUpdatesWithNoChange) { mySuppressUpdatesWithNoChange = theSuppressUpdatesWithNoChange; } /** * This setting may be used to advise the server that any references found in * resources that have any of the base URLs given here will be replaced with * simple local references. * <p> * For example, if the set contains the value <code>http://example.com/base/</code> * and a resource is submitted to the server that contains a reference to * <code>http://example.com/base/Patient/1</code>, the server will automatically * convert this reference to <code>Patient/1</code> * </p> * * @param theTreatBaseUrlsAsLocal * The set of base URLs. May be <code>null</code>, which * means no references will be treated as external */ public void setTreatBaseUrlsAsLocal(Set<String> theTreatBaseUrlsAsLocal) { if (theTreatBaseUrlsAsLocal != null) { for (String next : theTreatBaseUrlsAsLocal) { validateTreatBaseUrlsAsLocal(next); } } HashSet<String> treatBaseUrlsAsLocal = new HashSet<String>(); for (String next : ObjectUtils.defaultIfNull(theTreatBaseUrlsAsLocal, new HashSet<String>())) { while (next.endsWith("/")) { next = next.substring(0, next.length() - 1); } treatBaseUrlsAsLocal.add(next); } myTreatBaseUrlsAsLocal = treatBaseUrlsAsLocal; } /** * This setting may be used to advise the server that any references found in * resources that have any of the base URLs given here will be treated as logical * references instead of being treated as real references. * <p> * A logical reference is a reference which is treated as an identifier, and * does not neccesarily resolve. See {@link "http://hl7.org/fhir/references.html"} for * a description of logical references. For example, the valueset * {@link "http://hl7.org/fhir/valueset-quantity-comparator.html"} is a logical * reference. * </p> * <p> * Values for this field may take either of the following forms: * </p> * <ul> * <li><code>http://example.com/some-url</code> <b>(will be matched exactly)</b></li> * <li><code>http://example.com/some-base*</code> <b>(will match anything beginning with the part before the *)</b></li> * </ul> * * @see #DEFAULT_LOGICAL_BASE_URLS Default values for this property */ public DaoConfig setTreatReferencesAsLogical(Set<String> theTreatReferencesAsLogical) { myTreatReferencesAsLogical = theTreatReferencesAsLogical; return this; } private static void validateTreatBaseUrlsAsLocal(String theUrl) { Validate.notBlank(theUrl, "Base URL must not be null or empty"); int starIdx = theUrl.indexOf('*'); if (starIdx != -1) { if (starIdx != theUrl.length() - 1) { throw new IllegalArgumentException("Base URL wildcard character (*) can only appear at the end of the string: " + theUrl); } } } }