From 8b46257423eb03f4b5bd84dd53f13564015d6cb3 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sun, 14 Oct 2018 09:32:07 -0400 Subject: [PATCH] Add warm cache module --- .../ca/uhn/fhir/jpa/config/BaseConfig.java | 7 + .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 8 +- .../fhir/jpa/dao/BaseSearchParamRegistry.java | 173 +- .../java/ca/uhn/fhir/jpa/dao/DaoConfig.java | 22 +- .../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 61 +- .../ResourceIndexedSearchParamString.java | 20 +- .../java/ca/uhn/fhir/jpa/entity/Search.java | 9 - .../search/PersistedJpaBundleProvider.java | 3 +- .../jpa/search/SearchCoordinatorSvcImpl.java | 56 +- .../jpa/search/warm/CacheWarmingSvcImpl.java | 96 ++ .../jpa/search/warm/ICacheWarmingSvc.java | 8 + .../fhir/jpa/search/warm/WarmCacheEntry.java | 61 + .../ca/uhn/fhir/jpa/config/TestR4Config.java | 3 - .../ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java | 5 +- .../r4/FhirResourceDaoR4CacheWarmingTest.java | 112 ++ .../FhirResourceDaoR4SearchOptimizedTest.java | 17 +- .../jpa/dao/r4/FhirResourceDaoR4SortTest.java | 19 +- .../tasks/HapiFhirJpaMigrationTasks.java | 2 +- .../org/hl7/fhir/instance/model/Order.java | 1512 +++++++++-------- src/changes/changes.xml | 5 + 20 files changed, 1291 insertions(+), 908 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/CacheWarmingSvcImpl.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/ICacheWarmingSvc.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/WarmCacheEntry.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CacheWarmingTest.java diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java index e94be10617a..3caab881180 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java @@ -25,6 +25,8 @@ import ca.uhn.fhir.i18n.HapiLocalizer; import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.provider.SubscriptionRetriggeringProvider; import ca.uhn.fhir.jpa.search.*; +import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl; +import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl; import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor; @@ -111,6 +113,11 @@ public abstract class BaseConfig implements SchedulingConfigurer { public abstract FhirContext fhirContext(); + @Bean + public ICacheWarmingSvc cacheWarmingSvc() { + return new CacheWarmingSvcImpl(); + } + @Bean public HibernateExceptionTranslator hibernateExceptionTranslator() { return new HibernateExceptionTranslator(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 19c9e5a2610..73dcf594fe5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -511,9 +511,13 @@ public abstract class BaseHapiFhirResourceDao extends B @Override @Transactional(propagation = Propagation.NEVER) public ExpungeOutcome expunge(IIdType theId, ExpungeOptions theExpungeOptions) { - BaseHasResource entity = readEntity(theId); + + TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager); + + BaseHasResource entity = txTemplate.execute(t->readEntity(theId)); if (theId.hasVersionIdPart()) { - BaseHasResource currentVersion = readEntity(theId.toVersionless()); + BaseHasResource currentVersion; + currentVersion = txTemplate.execute(t->readEntity(theId.toVersionless())); if (entity.getVersion() == currentVersion.getVersion()) { throw new PreconditionFailedException("Can not perform version-specific expunge of resource " + theId.toUnqualified().getValue() + " as this is the current version"); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java index 2589b615759..e448611536b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao; * 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. @@ -24,9 +24,9 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam; -import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.util.SearchParameterUtil; +import ca.uhn.fhir.util.StopWatch; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -37,6 +37,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.TransactionTemplate; import javax.annotation.PostConstruct; import java.util.*; @@ -58,7 +60,8 @@ public abstract class BaseSearchParamRegistry implemen private DaoConfig myDaoConfig; private volatile long myLastRefresh; private ApplicationContext myApplicationContext; - + @Autowired + private PlatformTransactionManager myTxManager; public BaseSearchParamRegistry() { super(); } @@ -197,7 +200,7 @@ public abstract class BaseSearchParamRegistry implemen }); for (String nextBase : next.getBase()) { if (!activeParamNamesToUniqueSearchParams.containsKey(nextBase)) { - activeParamNamesToUniqueSearchParams.put(nextBase, new HashMap, List>()); + activeParamNamesToUniqueSearchParams.put(nextBase, new HashMap<>()); } if (!activeParamNamesToUniqueSearchParams.get(nextBase).containsKey(paramNames)) { activeParamNamesToUniqueSearchParams.get(nextBase).put(paramNames, new ArrayList()); @@ -242,86 +245,94 @@ public abstract class BaseSearchParamRegistry implemen long refreshInterval = 60 * DateUtils.MILLIS_PER_MINUTE; if (System.currentTimeMillis() - refreshInterval > myLastRefresh) { synchronized (this) { - if (System.currentTimeMillis() - refreshInterval > myLastRefresh) { - StopWatch sw = new StopWatch(); + TransactionTemplate txTemplate = new TransactionTemplate(myTxManager); + txTemplate.execute(t->{ + doRefresh(refreshInterval); + return null; + }); + } + } + } - Map> searchParams = new HashMap<>(); - for (Map.Entry> nextBuiltInEntry : getBuiltInSearchParams().entrySet()) { - for (RuntimeSearchParam nextParam : nextBuiltInEntry.getValue().values()) { - String nextResourceName = nextBuiltInEntry.getKey(); - getSearchParamMap(searchParams, nextResourceName).put(nextParam.getName(), nextParam); - } - } + private void doRefresh(long theRefreshInterval) { + if (System.currentTimeMillis() - theRefreshInterval > myLastRefresh) { + StopWatch sw = new StopWatch(); - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(MAX_MANAGED_PARAM_COUNT); - - IBundleProvider allSearchParamsBp = getSearchParameterDao().search(params); - int size = allSearchParamsBp.size(); - - // Just in case.. - if (size >= MAX_MANAGED_PARAM_COUNT) { - ourLog.warn("Unable to support >" + MAX_MANAGED_PARAM_COUNT + " search params!"); - size = MAX_MANAGED_PARAM_COUNT; - } - - List allSearchParams = allSearchParamsBp.getResources(0, size); - for (IBaseResource nextResource : allSearchParams) { - SP nextSp = (SP) nextResource; - if (nextSp == null) { - continue; - } - - RuntimeSearchParam runtimeSp = toRuntimeSp(nextSp); - if (runtimeSp == null) { - continue; - } - - for (String nextBaseName : SearchParameterUtil.getBaseAsStrings(myCtx, nextSp)) { - if (isBlank(nextBaseName)) { - continue; - } - - Map searchParamMap = getSearchParamMap(searchParams, nextBaseName); - String name = runtimeSp.getName(); - if (myDaoConfig.isDefaultSearchParamsCanBeOverridden() || !searchParamMap.containsKey(name)) { - searchParamMap.put(name, runtimeSp); - } - - } - } - - Map> activeSearchParams = new HashMap<>(); - for (Map.Entry> nextEntry : searchParams.entrySet()) { - for (RuntimeSearchParam nextSp : nextEntry.getValue().values()) { - String nextName = nextSp.getName(); - if (nextSp.getStatus() != RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE) { - nextSp = null; - } - - if (!activeSearchParams.containsKey(nextEntry.getKey())) { - activeSearchParams.put(nextEntry.getKey(), new HashMap<>()); - } - if (activeSearchParams.containsKey(nextEntry.getKey())) { - ourLog.debug("Replacing existing/built in search param {}:{} with new one", nextEntry.getKey(), nextName); - } - - if (nextSp != null) { - activeSearchParams.get(nextEntry.getKey()).put(nextName, nextSp); - } else { - activeSearchParams.get(nextEntry.getKey()).remove(nextName); - } - } - } - - myActiveSearchParams = activeSearchParams; - - populateActiveSearchParams(activeSearchParams); - - myLastRefresh = System.currentTimeMillis(); - ourLog.info("Refreshed search parameter cache in {}ms", sw.getMillis()); + Map> searchParams = new HashMap<>(); + for (Map.Entry> nextBuiltInEntry : getBuiltInSearchParams().entrySet()) { + for (RuntimeSearchParam nextParam : nextBuiltInEntry.getValue().values()) { + String nextResourceName = nextBuiltInEntry.getKey(); + getSearchParamMap(searchParams, nextResourceName).put(nextParam.getName(), nextParam); } } + + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(MAX_MANAGED_PARAM_COUNT); + + IBundleProvider allSearchParamsBp = getSearchParameterDao().search(params); + int size = allSearchParamsBp.size(); + + // Just in case.. + if (size >= MAX_MANAGED_PARAM_COUNT) { + ourLog.warn("Unable to support >" + MAX_MANAGED_PARAM_COUNT + " search params!"); + size = MAX_MANAGED_PARAM_COUNT; + } + + List allSearchParams = allSearchParamsBp.getResources(0, size); + for (IBaseResource nextResource : allSearchParams) { + SP nextSp = (SP) nextResource; + if (nextSp == null) { + continue; + } + + RuntimeSearchParam runtimeSp = toRuntimeSp(nextSp); + if (runtimeSp == null) { + continue; + } + + for (String nextBaseName : SearchParameterUtil.getBaseAsStrings(myCtx, nextSp)) { + if (isBlank(nextBaseName)) { + continue; + } + + Map searchParamMap = getSearchParamMap(searchParams, nextBaseName); + String name = runtimeSp.getName(); + if (myDaoConfig.isDefaultSearchParamsCanBeOverridden() || !searchParamMap.containsKey(name)) { + searchParamMap.put(name, runtimeSp); + } + + } + } + + Map> activeSearchParams = new HashMap<>(); + for (Map.Entry> nextEntry : searchParams.entrySet()) { + for (RuntimeSearchParam nextSp : nextEntry.getValue().values()) { + String nextName = nextSp.getName(); + if (nextSp.getStatus() != RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE) { + nextSp = null; + } + + if (!activeSearchParams.containsKey(nextEntry.getKey())) { + activeSearchParams.put(nextEntry.getKey(), new HashMap<>()); + } + if (activeSearchParams.containsKey(nextEntry.getKey())) { + ourLog.debug("Replacing existing/built in search param {}:{} with new one", nextEntry.getKey(), nextName); + } + + if (nextSp != null) { + activeSearchParams.get(nextEntry.getKey()).put(nextName, nextSp); + } else { + activeSearchParams.get(nextEntry.getKey()).remove(nextName); + } + } + } + + myActiveSearchParams = activeSearchParams; + + populateActiveSearchParams(activeSearchParams); + + myLastRefresh = System.currentTimeMillis(); + ourLog.info("Refreshed search parameter cache in {}ms", sw.getMillis()); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java index 6cc035be6ae..eee2cc4614f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum; +import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry; import ca.uhn.fhir.jpa.util.JpaConstants; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import com.google.common.collect.Sets; @@ -22,9 +23,9 @@ import java.util.*; * 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. @@ -153,6 +154,7 @@ public class DaoConfig { private Set myBundleTypesAllowedForStorage; private boolean myValidateSearchParameterExpressionsOnSave = true; private List myPreFetchThresholds = Arrays.asList(500, 2000, -1); + private List myWarmCacheEntries = new ArrayList<>(); /** * Constructor @@ -171,6 +173,22 @@ public class DaoConfig { } } + /** + * Returns a set of searches that should be kept "warm", meaning that + * searches will periodically be performed in the background to + * keep results ready for this search + */ + public List getWarmCacheEntries() { + if (myWarmCacheEntries == null) { + myWarmCacheEntries = new ArrayList<>(); + } + return myWarmCacheEntries; + } + + public void setWarmCacheEntries(List theWarmCacheEntries) { + myWarmCacheEntries = theWarmCacheEntries; + } + /** * If set to true (default is false), the reindexing of search parameters * using a query on the HFJ_RESOURCE.SP_INDEX_STATUS column will be disabled completely. diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index b0fdfe956cc..c26d69851be 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -1359,6 +1359,8 @@ public class SearchBuilder implements ISearchBuilder { } private TypedQuery createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount) { + myPredicates = new ArrayList<>(); + CriteriaQuery outerQuery; /* * Sort @@ -1369,30 +1371,48 @@ public class SearchBuilder implements ISearchBuilder { if (sort != null) { assert !theCount; +// outerQuery = myBuilder.createQuery(Long.class); +// Root outerQueryFrom = outerQuery.from(ResourceTable.class); +// +// List orders = Lists.newArrayList(); +// List predicates = Lists.newArrayList(); +// +// createSort(myBuilder, outerQueryFrom, sort, orders, predicates); +// if (orders.size() > 0) { +// outerQuery.orderBy(orders); +// } +// +// Subquery subQ = outerQuery.subquery(Long.class); +// Root subQfrom = subQ.from(ResourceTable.class); +// +// myResourceTableQuery = subQ; +// myResourceTableRoot = subQfrom; +// +// Expression selectExpr = subQfrom.get("myId").as(Long.class); +// subQ.select(selectExpr); +// +// predicates.add(0, myBuilder.in(outerQueryFrom.get("myId").as(Long.class)).value(subQ)); +// +// outerQuery.multiselect(outerQueryFrom.get("myId").as(Long.class)); +// outerQuery.where(predicates.toArray(new Predicate[0])); + outerQuery = myBuilder.createQuery(Long.class); - Root outerQueryFrom = outerQuery.from(ResourceTable.class); + myResourceTableQuery = outerQuery; + myResourceTableRoot = myResourceTableQuery.from(ResourceTable.class); + if (theCount) { + outerQuery.multiselect(myBuilder.countDistinct(myResourceTableRoot)); + } else { + outerQuery.multiselect(myResourceTableRoot.get("myId").as(Long.class)); + } List orders = Lists.newArrayList(); - List predicates = Lists.newArrayList(); + List predicates = myPredicates; // Lists.newArrayList(); - createSort(myBuilder, outerQueryFrom, sort, orders, predicates); + createSort(myBuilder, myResourceTableRoot, sort, orders, predicates); if (orders.size() > 0) { outerQuery.orderBy(orders); } - Subquery subQ = outerQuery.subquery(Long.class); - Root subQfrom = subQ.from(ResourceTable.class); - - myResourceTableQuery = subQ; - myResourceTableRoot = subQfrom; - - Expression selectExpr = subQfrom.get("myId").as(Long.class); - subQ.select(selectExpr); - - predicates.add(0, myBuilder.in(outerQueryFrom.get("myId").as(Long.class)).value(subQ)); - - outerQuery.multiselect(outerQueryFrom.get("myId").as(Long.class)); - outerQuery.where(predicates.toArray(new Predicate[0])); } else { @@ -1407,8 +1427,6 @@ public class SearchBuilder implements ISearchBuilder { } - myPredicates = new ArrayList<>(); - if (myParams.getEverythingMode() != null) { Join join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT); @@ -1590,7 +1608,8 @@ public class SearchBuilder implements ISearchBuilder { if (param.getParamType() == RestSearchParameterTypeEnum.REFERENCE) { thePredicates.add(join.get("mySourcePath").as(String.class).in(param.getPathsSplit())); } else { - Predicate joinParam1 = theBuilder.equal(join.get("myParamName"), theSort.getParamName()); + Long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(myResourceName, theSort.getParamName()); + Predicate joinParam1 = theBuilder.equal(join.get("myHashIdentity"), hashIdentity); thePredicates.add(joinParam1); } } else { @@ -1940,11 +1959,11 @@ public class SearchBuilder implements ISearchBuilder { return; } - if (theParamName.equals(BaseResource.SP_RES_ID)) { + if (theParamName.equals(IAnyResource.SP_RES_ID)) { addPredicateResourceId(theAndOrParams); - } else if (theParamName.equals(BaseResource.SP_RES_LANGUAGE)) { + } else if (theParamName.equals(IAnyResource.SP_RES_LANGUAGE)) { addPredicateLanguage(theAndOrParams); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java index 1ee6dfccd46..5c6ba1e4e0e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.entity; * 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. @@ -44,6 +44,10 @@ import static org.apache.commons.lang3.StringUtils.left; * do not reuse these names: * IDX_SP_STRING */ + + // This one us used only for sorting + @Index(name = "IDX_SP_STRING_HASH_IDENT", columnList = "HASH_IDENTITY"), + @Index(name = "IDX_SP_STRING_HASH_NRM", columnList = "HASH_NORM_PREFIX,SP_VALUE_NORMALIZED"), @Index(name = "IDX_SP_STRING_HASH_EXCT", columnList = "HASH_EXACT"), @@ -130,6 +134,11 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP */ @Column(name = "HASH_NORM_PREFIX", nullable = true) private Long myHashNormalizedPrefix; + /** + * @since 3.6.0 - At some point this should be made not-null + */ + @Column(name = "HASH_IDENTITY", nullable = true) + private Long myHashIdentity; /** * @since 3.4.0 - At some point this should be made not-null */ @@ -137,12 +146,10 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP private Long myHashExact; @Transient private transient DaoConfig myDaoConfig; - public ResourceIndexedSearchParamString() { super(); } - public ResourceIndexedSearchParamString(DaoConfig theDaoConfig, String theName, String theValueNormalized, String theValueExact) { setDaoConfig(theDaoConfig); setParamName(theName); @@ -150,6 +157,10 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP setValueExact(theValueExact); } + public void setHashIdentity(Long theHashIdentity) { + myHashIdentity = theHashIdentity; + } + @PrePersist public void calculateHashes() { if (myHashNormalizedPrefix == null && myDaoConfig != null) { @@ -159,6 +170,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP String valueExact = getValueExact(); setHashNormalizedPrefix(calculateHashNormalized(myDaoConfig, resourceType, paramName, valueNormalized)); setHashExact(calculateHashExact(resourceType, paramName, valueExact)); + setHashIdentity(calculateHashIdentity(resourceType, paramName)); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/Search.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/Search.java index 35722280124..f8737108eed 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/Search.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/Search.java @@ -5,8 +5,6 @@ import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.rest.param.DateRangeParam; import org.apache.commons.lang3.SerializationUtils; import org.hibernate.annotations.OptimisticLock; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.persistence.*; import javax.validation.constraints.NotNull; @@ -150,13 +148,6 @@ public class Search implements Serializable { myFailureMessage = left(theFailureMessage, FAILURE_MESSAGE_LENGTH); } - // FIXME: remove this - private static final Logger ourLog = LoggerFactory.getLogger(Search.class); - @PrePersist - public void prePersist() { - ourLog.info("*** SAVING WITH VERSION {} TOTAL {}", myVersion, myTotalCount); - } - public Long getId() { return myId; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java index 1d9f51f4818..06de297a582 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java @@ -166,8 +166,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider { return false; } - // FIXME: remove - ourLog.info("** Retrieved search with version {} and total {}", mySearchEntity.getVersion(), mySearchEntity.getTotalCount()); + ourLog.trace("Retrieved search with version {} and total {}", mySearchEntity.getVersion(), mySearchEntity.getTotalCount()); // Load the includes now so that they are available outside of this transaction mySearchEntity.getIncludes().size(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index ecfb1b305ac..d3a523d9b4e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -46,9 +46,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.time.DateUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.*; import org.springframework.scheduling.concurrent.CustomizableThreadFactory; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; @@ -363,7 +361,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { PersistedJpaSearchFirstPageBundleProvider retVal = new PersistedJpaSearchFirstPageBundleProvider(search, theCallingDao, task, sb, myManagedTxManager); populateBundleProvider(retVal); - ourLog.info("Search initial phase completed in {}ms", w.getMillis()); + ourLog.debug("Search initial phase completed in {}ms", w.getMillis()); return retVal; } @@ -675,25 +673,21 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { private void doSaveSearch() { - // FIXME: remove - Integer totalCount = mySearch.getTotalCount(); - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { - @Override - public void afterCommit() { - ourLog.info("Have flushed save with total {}", totalCount); - } - }); - ourLog.info("** Saving search version {} count {}", mySearch.getVersion(), totalCount); - + Search newSearch; if (mySearch.getId() == null) { - mySearch = mySearchDao.saveAndFlush(mySearch); + newSearch = mySearchDao.save(mySearch); for (SearchInclude next : mySearch.getIncludes()) { mySearchIncludeDao.save(next); } } else { - mySearch = mySearchDao.saveAndFlush(mySearch); + newSearch = mySearchDao.save(mySearch); } + // mySearchDao.save is not supposed to return null, but in unit tests + // it can if the mock search dao isn't set up to handle that + if (newSearch != null) { + mySearch = newSearch; + } } /** @@ -712,14 +706,13 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { boolean wantCount = myParams.getSummaryMode().contains(SummaryEnum.COUNT); boolean wantOnlyCount = wantCount && myParams.getSummaryMode().size() == 1; if (wantCount) { - ourLog.info("** performing count"); + ourLog.trace("Performing count"); ISearchBuilder sb = newSearchBuilder(); Iterator countIterator = sb.createCountQuery(myParams, mySearch.getUuid()); Long count = countIterator.next(); - ourLog.info("** got count {}", count); + ourLog.trace("Got count {}", count); TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager); -// txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); txTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus theArg0) { @@ -728,6 +721,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { mySearch.setStatus(SearchStatusEnum.FINISHED); } doSaveSearch(); + mySearchDao.flush(); } }); if (wantOnlyCount) { @@ -735,7 +729,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { } } - ourLog.info("** done count"); + ourLog.trace("Done count"); ISearchBuilder sb = newSearchBuilder(); /* @@ -957,13 +951,33 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { int pageIndex = theFromIndex / pageSize; - Pageable page = new PageRequest(pageIndex, pageSize) { + Pageable page = new AbstractPageRequest(pageIndex, pageSize) { private static final long serialVersionUID = 1L; @Override public long getOffset() { return theFromIndex; } + + @Override + public Sort getSort() { + return Sort.unsorted(); + } + + @Override + public Pageable next() { + return null; + } + + @Override + public Pageable previous() { + return null; + } + + @Override + public Pageable first() { + return null; + } }; return page; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/CacheWarmingSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/CacheWarmingSvcImpl.java new file mode 100644 index 00000000000..1a57b4033d1 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/CacheWarmingSvcImpl.java @@ -0,0 +1,96 @@ +package ca.uhn.fhir.jpa.search.warm; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jpa.dao.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class CacheWarmingSvcImpl implements ICacheWarmingSvc { + + @Autowired + private DaoConfig myDaoConfig; + private Map myCacheEntryToNextRefresh = new LinkedHashMap<>(); + @Autowired + private FhirContext myCtx; + @Autowired + private DaoRegistry myDaoRegistry; + + @Override + @Scheduled(fixedDelay = 1000) + public synchronized void performWarmingPass() { + + for (WarmCacheEntry nextCacheEntry : new ArrayList<>(myCacheEntryToNextRefresh.keySet())) { + + long nextRefresh = myCacheEntryToNextRefresh.get(nextCacheEntry); + if (nextRefresh < System.currentTimeMillis()) { + + // Perform the search + refreshNow(nextCacheEntry); + + // Set the next time to warm this search + nextRefresh = nextCacheEntry.getPeriodMillis() + System.currentTimeMillis(); + myCacheEntryToNextRefresh.put(nextCacheEntry, nextRefresh); + + } + + } + + } + + private void refreshNow(WarmCacheEntry theCacheEntry) { + String nextUrl = theCacheEntry.getUrl(); + + RuntimeResourceDefinition resourceDef = parseWarmUrlResourceType(nextUrl); + IFhirResourceDao callingDao = myDaoRegistry.getResourceDao(resourceDef.getName()); + String queryPart = parseWarmUrlParamPart(nextUrl); + SearchParameterMap responseCriteriaUrl = BaseHapiFhirDao.translateMatchUrl(callingDao, myCtx, queryPart, resourceDef); + + callingDao.search(responseCriteriaUrl); + } + + private String parseWarmUrlParamPart(String theNextUrl) { + int paramIndex = theNextUrl.indexOf('?'); + if (paramIndex == -1) { + throw new ConfigurationException("Invalid warm cache URL (must have ? character)"); + } + return theNextUrl.substring(paramIndex); + } + + private RuntimeResourceDefinition parseWarmUrlResourceType(String theNextUrl) { + int paramIndex = theNextUrl.indexOf('?'); + String resourceName = theNextUrl.substring(0, paramIndex); + if (resourceName.contains("/")) { + resourceName = resourceName.substring(resourceName.lastIndexOf('/') + 1); + } + RuntimeResourceDefinition resourceDef = myCtx.getResourceDefinition(resourceName); + return resourceDef; + } + + @PostConstruct + public void start() { + initCacheMap(); + } + + public synchronized void initCacheMap() { + + myCacheEntryToNextRefresh.clear(); + List warmCacheEntries = myDaoConfig.getWarmCacheEntries(); + for (WarmCacheEntry next : warmCacheEntries) { + + // Validate + parseWarmUrlParamPart(next.getUrl()); + parseWarmUrlResourceType(next.getUrl()); + + myCacheEntryToNextRefresh.put(next, 0L); + } + + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/ICacheWarmingSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/ICacheWarmingSvc.java new file mode 100644 index 00000000000..00ca3c61ede --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/ICacheWarmingSvc.java @@ -0,0 +1,8 @@ +package ca.uhn.fhir.jpa.search.warm; + +import org.springframework.scheduling.annotation.Scheduled; + +public interface ICacheWarmingSvc { + @Scheduled(fixedDelay = 1000) + void performWarmingPass(); +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/WarmCacheEntry.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/WarmCacheEntry.java new file mode 100644 index 00000000000..bdb55b0c0b8 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/WarmCacheEntry.java @@ -0,0 +1,61 @@ +package ca.uhn.fhir.jpa.search.warm; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +/** + * Denotes a search that should be performed in the background + * periodically in order to keep a fresh copy in the query cache. + * This improves performance for searches by keeping a copy + * loaded in the background. + */ +public class WarmCacheEntry { + + private long myPeriodMillis; + private String myUrl; + + @Override + public boolean equals(Object theO) { + if (this == theO) { + return true; + } + + if (theO == null || getClass() != theO.getClass()) { + return false; + } + + WarmCacheEntry that = (WarmCacheEntry) theO; + + return new EqualsBuilder() + .append(myPeriodMillis, that.myPeriodMillis) + .append(myUrl, that.myUrl) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(myPeriodMillis) + .append(myUrl) + .toHashCode(); + } + + public long getPeriodMillis() { + return myPeriodMillis; + } + + public WarmCacheEntry setPeriodMillis(long thePeriodMillis) { + myPeriodMillis = thePeriodMillis; + return this; + } + + public String getUrl() { + return myUrl; + } + + public WarmCacheEntry setUrl(String theUrl) { + myUrl = theUrl; + return this; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index 648c56d92a6..b5ca9fe24b4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -6,9 +6,6 @@ import ca.uhn.fhir.validation.ResultSeverityEnum; import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import org.apache.commons.dbcp2.BasicDataSource; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.query.criteria.LiteralHandlingMode; -import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java index affb989a7ab..bbfc58077fd 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java @@ -11,6 +11,7 @@ import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; +import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; @@ -51,7 +52,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Map; -import static org.junit.Assert.*; +import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; @RunWith(SpringJUnit4ClassRunner.class) @@ -260,6 +261,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Autowired protected ITermConceptMapGroupElementTargetDao myTermConceptMapGroupElementTargetDao; @Autowired + protected ICacheWarmingSvc myCacheWarmingSvc; + @Autowired private JpaValidationSupportChainR4 myJpaValidationSupportChainR4; @After() diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CacheWarmingTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CacheWarmingTest.java new file mode 100644 index 00000000000..c71c6af5ce4 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CacheWarmingTest.java @@ -0,0 +1,112 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; +import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl; +import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.util.TestUtil; +import org.hl7.fhir.r4.model.Patient; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class FhirResourceDaoR4CacheWarmingTest extends BaseJpaR4Test { + private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4CacheWarmingTest.class); + + @After + public void afterResetDao() { + myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); + + myDaoConfig.setWarmCacheEntries(new ArrayList<>()); + CacheWarmingSvcImpl cacheWarmingSvc = (CacheWarmingSvcImpl) myCacheWarmingSvc; + cacheWarmingSvc.initCacheMap(); + } + + + @Test + public void testInvalidCacheEntries() { + CacheWarmingSvcImpl cacheWarmingSvc = (CacheWarmingSvcImpl) myCacheWarmingSvc; + + myDaoConfig.setWarmCacheEntries(new ArrayList<>()); + myDaoConfig.getWarmCacheEntries().add( + new WarmCacheEntry() + .setPeriodMillis(10) + .setUrl("BadResource?name=smith") + ); + try { + cacheWarmingSvc.initCacheMap(); + fail(); + } catch (DataFormatException e) { + assertEquals("Unknown resource name \"BadResource\" (this name is not known in FHIR version \"R4\")", e.getMessage()); + } + + myDaoConfig.setWarmCacheEntries(new ArrayList<>()); + myDaoConfig.getWarmCacheEntries().add( + new WarmCacheEntry() + .setPeriodMillis(10) + .setUrl("foo/Patient") + ); + try { + cacheWarmingSvc.initCacheMap(); + fail(); + } catch (ConfigurationException e) { + assertEquals("Invalid warm cache URL (must have ? character)", e.getMessage()); + } + + + } + + @Test + public void testKeepCacheWarm() throws InterruptedException { + myDaoConfig.setWarmCacheEntries(new ArrayList<>()); + myDaoConfig.getWarmCacheEntries().add( + new WarmCacheEntry() + .setPeriodMillis(10) + .setUrl("Patient?name=smith") + ); + CacheWarmingSvcImpl cacheWarmingSvc = (CacheWarmingSvcImpl) myCacheWarmingSvc; + cacheWarmingSvc.initCacheMap(); + + Patient p1 = new Patient(); + p1.setId("p1"); + p1.setActive(true); + myPatientDao.update(p1); + + Patient p2 = new Patient(); + p2.setId("p2"); + p2.setActive(true); + p2.addName().setFamily("Smith"); + myPatientDao.update(p2); + + Thread.sleep(2000); + + SearchParameterMap params = new SearchParameterMap(); + params.add("name", new StringParam("smith")); + IBundleProvider result = myPatientDao.search(params); + assertEquals(PersistedJpaBundleProvider.class, result.getClass()); + + PersistedJpaBundleProvider resultCasted = (PersistedJpaBundleProvider) result; + assertTrue(resultCasted.isCacheHit()); + } + + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java index eb75c0fecd5..a7461c7e50b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java @@ -70,6 +70,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { params.setSummaryMode(Sets.newHashSet(SummaryEnum.COUNT)); IBundleProvider results = myPatientDao.search(params); String uuid = results.getUuid(); + ourLog.info("** Search returned UUID: {}", uuid); assertEquals(200, results.size().intValue()); List ids = toUnqualifiedVersionlessIdValues(results, 0, 10, true); assertThat(ids, empty()); @@ -98,6 +99,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { params.setSummaryMode(Sets.newHashSet(SummaryEnum.COUNT)); IBundleProvider results = myPatientDao.search(params); String uuid = results.getUuid(); + ourLog.info("** Search returned UUID: {}", uuid); assertEquals(201, results.size().intValue()); List ids = toUnqualifiedVersionlessIdValues(results, 0, 10, true); assertThat(ids, empty()); @@ -109,6 +111,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { params.setSummaryMode(Sets.newHashSet(SummaryEnum.COUNT, SummaryEnum.DATA)); results = myPatientDao.search(params); uuid = results.getUuid(); + ourLog.info("** Search returned UUID: {}", uuid); assertEquals(201, results.size().intValue()); ids = toUnqualifiedVersionlessIdValues(results, 0, 10, true); assertThat(ids, hasSize(10)); @@ -120,6 +123,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { params.setSummaryMode(Sets.newHashSet(SummaryEnum.COUNT)); results = myPatientDao.search(params); uuid = results.getUuid(); + ourLog.info("** Search returned UUID: {}", uuid); assertEquals(201, results.size().intValue()); ids = toUnqualifiedVersionlessIdValues(results, 0, 10, true); assertThat(ids, empty()); @@ -144,12 +148,15 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { // assertEquals(200, myDatabaseBackedPagingProvider.retrieveResultList(uuid).size().intValue()); assertEquals(200, results.size().intValue()); + ourLog.info("** Asking for results"); List ids = toUnqualifiedVersionlessIdValues(results, 0, 5, true); assertEquals("Patient/PT00000", ids.get(0)); assertEquals("Patient/PT00004", ids.get(4)); ourLog.info("** About to make new query for search with UUID: {}", uuid); - assertEquals(200, myDatabaseBackedPagingProvider.retrieveResultList(uuid).size().intValue()); + IBundleProvider search2 = myDatabaseBackedPagingProvider.retrieveResultList(uuid); + Integer search2Size = search2.size(); + assertEquals(200, search2Size.intValue()); } @Test @@ -162,6 +169,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { params.setSummaryMode(Sets.newHashSet(SummaryEnum.COUNT, SummaryEnum.DATA)); IBundleProvider results = myPatientDao.search(params); String uuid = results.getUuid(); + ourLog.info("** Search returned UUID: {}", uuid); assertEquals(200, results.size().intValue()); List ids = toUnqualifiedVersionlessIdValues(results, 0, 10, true); assertEquals("Patient/PT00000", ids.get(0)); @@ -176,6 +184,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { params.setSummaryMode(Sets.newHashSet(SummaryEnum.COUNT, SummaryEnum.DATA)); results = myPatientDao.search(params); uuid = results.getUuid(); + ourLog.info("** Search returned UUID: {}", uuid); assertEquals(200, results.size().intValue()); ids = toUnqualifiedVersionlessIdValues(results, 0, 10, true); assertEquals("Patient/PT00000", ids.get(0)); @@ -197,6 +206,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { params.setSort(new SortSpec(Patient.SP_NAME)); IBundleProvider results = myPatientDao.search(params); String uuid = results.getUuid(); + ourLog.info("** Search returned UUID: {}", uuid); List ids = toUnqualifiedVersionlessIdValues(results, 0, 200, true); assertEquals("Patient/PT00000", ids.get(0)); assertEquals("Patient/PT00199", ids.get(199)); @@ -250,6 +260,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { params.setSort(new SortSpec(Patient.SP_NAME)); IBundleProvider results = myPatientDao.search(params); String uuid = results.getUuid(); + ourLog.info("** Search returned UUID: {}", uuid); List ids = toUnqualifiedVersionlessIdValues(results, 0, 10, true); assertEquals("Patient/PT00000", ids.get(0)); assertEquals("Patient/PT00009", ids.get(9)); @@ -375,6 +386,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { params.setCount(50); IBundleProvider results = myPatientDao.search(params); String uuid = results.getUuid(); + ourLog.info("** Search returned UUID: {}", uuid); List ids = toUnqualifiedVersionlessIdValues(results, 0, 50, true); assertEquals("Patient/PT00000", ids.get(0)); assertEquals("Patient/PT00049", ids.get(49)); @@ -408,6 +420,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { params.setSort(new SortSpec(Patient.SP_NAME)); IBundleProvider results = myPatientDao.search(params); String uuid = results.getUuid(); + ourLog.info("** Search returned UUID: {}", uuid); List ids = toUnqualifiedVersionlessIdValues(results, 0, 10, true); assertEquals("Patient/PT00000", ids.get(0)); assertEquals("Patient/PT00009", ids.get(9)); @@ -465,6 +478,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { params.setSort(new SortSpec(Patient.SP_NAME)); final IBundleProvider results = myPatientDao.search(params); String uuid = results.getUuid(); + ourLog.info("** Search returned UUID: {}", uuid); List ids = toUnqualifiedVersionlessIdValues(results, 0, 10, true); assertEquals("Patient/PT00000", ids.get(0)); assertEquals("Patient/PT00009", ids.get(9)); @@ -529,6 +543,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { params.add(Patient.SP_RES_ID, new TokenParam("PT00000")); IBundleProvider results = myPatientDao.search(params); String uuid = results.getUuid(); + ourLog.info("** Search returned UUID: {}", uuid); List ids = toUnqualifiedVersionlessIdValues(results, 0, 10, true); assertEquals("Patient/PT00000", ids.get(0)); assertEquals(1, ids.size()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SortTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SortTest.java index 3336da449a8..e74af3b5b27 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SortTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SortTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.dao.r4; +import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.SearchParameterMap; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.SortOrderEnum; @@ -206,31 +207,38 @@ public class FhirResourceDaoR4SortTest extends BaseJpaR4Test { @SuppressWarnings("unused") @Test public void testSortOnSparselyPopulatedFields() { +// myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED); + IIdType pid1, pid2, pid3, pid4, pid5, pid6; { Patient p = new Patient(); + p.setId("pid1"); p.setActive(true); - pid1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + pid1 = myPatientDao.update(p, mySrd).getId().toUnqualifiedVersionless(); } { Patient p = new Patient(); + p.setId("pid2"); p.addName().setFamily("A"); - pid2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + pid2 = myPatientDao.update(p, mySrd).getId().toUnqualifiedVersionless(); } { Patient p = new Patient(); + p.setId("pid3"); p.addName().setFamily("B"); - pid3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + pid3 = myPatientDao.update(p, mySrd).getId().toUnqualifiedVersionless(); } { Patient p = new Patient(); + p.setId("pid4"); p.addName().setFamily("B").addGiven("A"); - pid4 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + pid4 = myPatientDao.update(p, mySrd).getId().toUnqualifiedVersionless(); } { Patient p = new Patient(); + p.setId("pid5"); p.addName().setFamily("B").addGiven("B"); - pid5 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + pid5 = myPatientDao.update(p, mySrd).getId().toUnqualifiedVersionless(); } SearchParameterMap map; @@ -239,6 +247,7 @@ public class FhirResourceDaoR4SortTest extends BaseJpaR4Test { map = new SearchParameterMap(); map.setSort(new SortSpec(Patient.SP_FAMILY, SortOrderEnum.ASC).setChain(new SortSpec(Patient.SP_GIVEN, SortOrderEnum.ASC))); ids = toUnqualifiedVersionlessIds(myPatientDao.search(map)); + ourLog.info("** Got IDs: {}", ids); assertThat(ids, contains(pid2, pid4, pid5, pid3, pid1)); assertEquals(5, ids.size()); diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index ff6813d333f..acaf3be1c6a 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -298,7 +298,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { "where HFJ_RES_PARAM_PRESENT.HASH_PRESENCE is null"; consolidateSearchParamPresenceIndexesTask.addQuery(sql, ArbitrarySqlTask.QueryModeEnum.BATCH_UNTIL_NO_MORE, t -> { Long pid = (Long) t.get("PID"); - Boolean present = (Boolean) t.get("HASH_PRESENCE"); + Boolean present = (Boolean) t.get("SP_PRESENT"); String resType = (String) t.get("RES_TYPE"); String paramName = (String) t.get("PARAM_NAME"); Long hash = SearchParamPresent.calculateHashPresence(resType, paramName, present); diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/model/Order.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/model/Order.java index 27e333b3365..cfc99908b1b 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/model/Order.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/model/Order.java @@ -1,755 +1,757 @@ -package org.hl7.fhir.instance.model; - -/* - Copyright (c) 2011+, HL7, Inc. - All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of HL7 nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - -*/ - -// Generated on Wed, Jul 13, 2016 05:32+1000 for FHIR v1.0.2 -import java.util.*; - -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.instance.model.api.IBaseBackboneElement; - -import ca.uhn.fhir.model.api.annotation.*; -/** - * A request to perform an action. - */ -@ResourceDef(name="Order", profile="http://hl7.org/fhir/Profile/Order") -public class Order extends DomainResource { - - @Block() - public static class OrderWhenComponent extends BackboneElement implements IBaseBackboneElement { - /** - * Code specifies when request should be done. The code may simply be a priority code. - */ - @Child(name = "code", type = {CodeableConcept.class}, order=1, min=0, max=1, modifier=false, summary=true) - @Description(shortDefinition="Code specifies when request should be done. The code may simply be a priority code", formalDefinition="Code specifies when request should be done. The code may simply be a priority code." ) - protected CodeableConcept code; - - /** - * A formal schedule. - */ - @Child(name = "schedule", type = {Timing.class}, order=2, min=0, max=1, modifier=false, summary=true) - @Description(shortDefinition="A formal schedule", formalDefinition="A formal schedule." ) - protected Timing schedule; - - private static final long serialVersionUID = 307115287L; - - /* - * Constructor - */ - public OrderWhenComponent() { - super(); - } - - /** - * @return {@link #code} (Code specifies when request should be done. The code may simply be a priority code.) - */ - public CodeableConcept getCode() { - if (this.code == null) - if (Configuration.errorOnAutoCreate()) - throw new Error("Attempt to auto-create OrderWhenComponent.code"); - else if (Configuration.doAutoCreate()) - this.code = new CodeableConcept(); // cc - return this.code; - } - - public boolean hasCode() { - return this.code != null && !this.code.isEmpty(); - } - - /** - * @param value {@link #code} (Code specifies when request should be done. The code may simply be a priority code.) - */ - public OrderWhenComponent setCode(CodeableConcept value) { - this.code = value; - return this; - } - - /** - * @return {@link #schedule} (A formal schedule.) - */ - public Timing getSchedule() { - if (this.schedule == null) - if (Configuration.errorOnAutoCreate()) - throw new Error("Attempt to auto-create OrderWhenComponent.schedule"); - else if (Configuration.doAutoCreate()) - this.schedule = new Timing(); // cc - return this.schedule; - } - - public boolean hasSchedule() { - return this.schedule != null && !this.schedule.isEmpty(); - } - - /** - * @param value {@link #schedule} (A formal schedule.) - */ - public OrderWhenComponent setSchedule(Timing value) { - this.schedule = value; - return this; - } - - protected void listChildren(List childrenList) { - super.listChildren(childrenList); - childrenList.add(new Property("code", "CodeableConcept", "Code specifies when request should be done. The code may simply be a priority code.", 0, java.lang.Integer.MAX_VALUE, code)); - childrenList.add(new Property("schedule", "Timing", "A formal schedule.", 0, java.lang.Integer.MAX_VALUE, schedule)); - } - - @Override - public void setProperty(String name, Base value) throws FHIRException { - if (name.equals("code")) - this.code = castToCodeableConcept(value); // CodeableConcept - else if (name.equals("schedule")) - this.schedule = castToTiming(value); // Timing - else - super.setProperty(name, value); - } - - @Override - public Base addChild(String name) throws FHIRException { - if (name.equals("code")) { - this.code = new CodeableConcept(); - return this.code; - } - else if (name.equals("schedule")) { - this.schedule = new Timing(); - return this.schedule; - } - else - return super.addChild(name); - } - - public OrderWhenComponent copy() { - OrderWhenComponent dst = new OrderWhenComponent(); - copyValues(dst); - dst.code = code == null ? null : code.copy(); - dst.schedule = schedule == null ? null : schedule.copy(); - return dst; - } - - @Override - public boolean equalsDeep(Base other) { - if (!super.equalsDeep(other)) - return false; - if (!(other instanceof OrderWhenComponent)) - return false; - OrderWhenComponent o = (OrderWhenComponent) other; - return compareDeep(code, o.code, true) && compareDeep(schedule, o.schedule, true); - } - - @Override - public boolean equalsShallow(Base other) { - if (!super.equalsShallow(other)) - return false; - if (!(other instanceof OrderWhenComponent)) - return false; - OrderWhenComponent o = (OrderWhenComponent) other; - return true; - } - - public boolean isEmpty() { - return super.isEmpty() && (code == null || code.isEmpty()) && (schedule == null || schedule.isEmpty()) - ; - } - - public String fhirType() { - return "Order.when"; - - } - - } - - /** - * Identifiers assigned to this order by the orderer or by the receiver. - */ - @Child(name = "identifier", type = {Identifier.class}, order=0, min=0, max=Child.MAX_UNLIMITED, modifier=false, summary=true) - @Description(shortDefinition="Identifiers assigned to this order by the orderer or by the receiver", formalDefinition="Identifiers assigned to this order by the orderer or by the receiver." ) - protected List identifier; - - /** - * When the order was made. - */ - @Child(name = "date", type = {DateTimeType.class}, order=1, min=0, max=1, modifier=false, summary=true) - @Description(shortDefinition="When the order was made", formalDefinition="When the order was made." ) - protected DateTimeType date; - - /** - * Patient this order is about. - */ - @Child(name = "subject", type = {Patient.class, Group.class, Device.class, Substance.class}, order=2, min=0, max=1, modifier=false, summary=true) - @Description(shortDefinition="Patient this order is about", formalDefinition="Patient this order is about." ) - protected Reference subject; - - /** - * The actual object that is the target of the reference (Patient this order is about.) - */ - protected Resource subjectTarget; - - /** - * Who initiated the order. - */ - @Child(name = "source", type = {Practitioner.class, Organization.class}, order=3, min=0, max=1, modifier=false, summary=true) - @Description(shortDefinition="Who initiated the order", formalDefinition="Who initiated the order." ) - protected Reference source; - - /** - * The actual object that is the target of the reference (Who initiated the order.) - */ - protected Resource sourceTarget; - - /** - * Who is intended to fulfill the order. - */ - @Child(name = "target", type = {Organization.class, Device.class, Practitioner.class}, order=4, min=0, max=1, modifier=false, summary=true) - @Description(shortDefinition="Who is intended to fulfill the order", formalDefinition="Who is intended to fulfill the order." ) - protected Reference target; - - /** - * The actual object that is the target of the reference (Who is intended to fulfill the order.) - */ - protected Resource targetTarget; - - /** - * Text - why the order was made. - */ - @Child(name = "reason", type = {CodeableConcept.class}, order=5, min=0, max=1, modifier=false, summary=true) - @Description(shortDefinition="Text - why the order was made", formalDefinition="Text - why the order was made." ) - protected Type reason; - - /** - * When order should be fulfilled. - */ - @Child(name = "when", type = {}, order=6, min=0, max=1, modifier=false, summary=true) - @Description(shortDefinition="When order should be fulfilled", formalDefinition="When order should be fulfilled." ) - protected OrderWhenComponent when; - - /** - * What action is being ordered. - */ - @Child(name = "detail", type = {}, order=7, min=1, max=Child.MAX_UNLIMITED, modifier=false, summary=true) - @Description(shortDefinition="What action is being ordered", formalDefinition="What action is being ordered." ) - protected List detail; - /** - * The actual objects that are the target of the reference (What action is being ordered.) - */ - protected List detailTarget; - - - private static final long serialVersionUID = -1392311096L; - - /* - * Constructor - */ - public Order() { - super(); - } - - /** - * @return {@link #identifier} (Identifiers assigned to this order by the orderer or by the receiver.) - */ - public List getIdentifier() { - if (this.identifier == null) - this.identifier = new ArrayList(); - return this.identifier; - } - - public boolean hasIdentifier() { - if (this.identifier == null) - return false; - for (Identifier item : this.identifier) - if (!item.isEmpty()) - return true; - return false; - } - - /** - * @return {@link #identifier} (Identifiers assigned to this order by the orderer or by the receiver.) - */ - // syntactic sugar - public Identifier addIdentifier() { //3 - Identifier t = new Identifier(); - if (this.identifier == null) - this.identifier = new ArrayList(); - this.identifier.add(t); - return t; - } - - // syntactic sugar - public Order addIdentifier(Identifier t) { //3 - if (t == null) - return this; - if (this.identifier == null) - this.identifier = new ArrayList(); - this.identifier.add(t); - return this; - } - - /** - * @return {@link #date} (When the order was made.). This is the underlying object with id, value and extensions. The accessor "getDate" gives direct access to the value - */ - public DateTimeType getDateElement() { - if (this.date == null) - if (Configuration.errorOnAutoCreate()) - throw new Error("Attempt to auto-create Order.date"); - else if (Configuration.doAutoCreate()) - this.date = new DateTimeType(); // bb - return this.date; - } - - public boolean hasDateElement() { - return this.date != null && !this.date.isEmpty(); - } - - public boolean hasDate() { - return this.date != null && !this.date.isEmpty(); - } - - /** - * @param value {@link #date} (When the order was made.). This is the underlying object with id, value and extensions. The accessor "getDate" gives direct access to the value - */ - public Order setDateElement(DateTimeType value) { - this.date = value; - return this; - } - - /** - * @return When the order was made. - */ - public Date getDate() { - return this.date == null ? null : this.date.getValue(); - } - - /** - * @param value When the order was made. - */ - public Order setDate(Date value) { - if (value == null) - this.date = null; - else { - if (this.date == null) - this.date = new DateTimeType(); - this.date.setValue(value); - } - return this; - } - - /** - * @return {@link #subject} (Patient this order is about.) - */ - public Reference getSubject() { - if (this.subject == null) - if (Configuration.errorOnAutoCreate()) - throw new Error("Attempt to auto-create Order.subject"); - else if (Configuration.doAutoCreate()) - this.subject = new Reference(); // cc - return this.subject; - } - - public boolean hasSubject() { - return this.subject != null && !this.subject.isEmpty(); - } - - /** - * @param value {@link #subject} (Patient this order is about.) - */ - public Order setSubject(Reference value) { - this.subject = value; - return this; - } - - /** - * @return {@link #subject} The actual object that is the target of the reference. The reference library doesn't populate this, but you can use it to hold the resource if you resolve it. (Patient this order is about.) - */ - public Resource getSubjectTarget() { - return this.subjectTarget; - } - - /** - * @param value {@link #subject} The actual object that is the target of the reference. The reference library doesn't use these, but you can use it to hold the resource if you resolve it. (Patient this order is about.) - */ - public Order setSubjectTarget(Resource value) { - this.subjectTarget = value; - return this; - } - - /** - * @return {@link #source} (Who initiated the order.) - */ - public Reference getSource() { - if (this.source == null) - if (Configuration.errorOnAutoCreate()) - throw new Error("Attempt to auto-create Order.source"); - else if (Configuration.doAutoCreate()) - this.source = new Reference(); // cc - return this.source; - } - - public boolean hasSource() { - return this.source != null && !this.source.isEmpty(); - } - - /** - * @param value {@link #source} (Who initiated the order.) - */ - public Order setSource(Reference value) { - this.source = value; - return this; - } - - /** - * @return {@link #source} The actual object that is the target of the reference. The reference library doesn't populate this, but you can use it to hold the resource if you resolve it. (Who initiated the order.) - */ - public Resource getSourceTarget() { - return this.sourceTarget; - } - - /** - * @param value {@link #source} The actual object that is the target of the reference. The reference library doesn't use these, but you can use it to hold the resource if you resolve it. (Who initiated the order.) - */ - public Order setSourceTarget(Resource value) { - this.sourceTarget = value; - return this; - } - - /** - * @return {@link #target} (Who is intended to fulfill the order.) - */ - public Reference getTarget() { - if (this.target == null) - if (Configuration.errorOnAutoCreate()) - throw new Error("Attempt to auto-create Order.target"); - else if (Configuration.doAutoCreate()) - this.target = new Reference(); // cc - return this.target; - } - - public boolean hasTarget() { - return this.target != null && !this.target.isEmpty(); - } - - /** - * @param value {@link #target} (Who is intended to fulfill the order.) - */ - public Order setTarget(Reference value) { - this.target = value; - return this; - } - - /** - * @return {@link #target} The actual object that is the target of the reference. The reference library doesn't populate this, but you can use it to hold the resource if you resolve it. (Who is intended to fulfill the order.) - */ - public Resource getTargetTarget() { - return this.targetTarget; - } - - /** - * @param value {@link #target} The actual object that is the target of the reference. The reference library doesn't use these, but you can use it to hold the resource if you resolve it. (Who is intended to fulfill the order.) - */ - public Order setTargetTarget(Resource value) { - this.targetTarget = value; - return this; - } - - /** - * @return {@link #reason} (Text - why the order was made.) - */ - public Type getReason() { - return this.reason; - } - - /** - * @return {@link #reason} (Text - why the order was made.) - */ - public CodeableConcept getReasonCodeableConcept() throws FHIRException { - if (!(this.reason instanceof CodeableConcept)) - throw new FHIRException("Type mismatch: the type CodeableConcept was expected, but "+this.reason.getClass().getName()+" was encountered"); - return (CodeableConcept) this.reason; - } - - public boolean hasReasonCodeableConcept() { - return this.reason instanceof CodeableConcept; - } - - /** - * @return {@link #reason} (Text - why the order was made.) - */ - public Reference getReasonReference() throws FHIRException { - if (!(this.reason instanceof Reference)) - throw new FHIRException("Type mismatch: the type Reference was expected, but "+this.reason.getClass().getName()+" was encountered"); - return (Reference) this.reason; - } - - public boolean hasReasonReference() { - return this.reason instanceof Reference; - } - - public boolean hasReason() { - return this.reason != null && !this.reason.isEmpty(); - } - - /** - * @param value {@link #reason} (Text - why the order was made.) - */ - public Order setReason(Type value) { - this.reason = value; - return this; - } - - /** - * @return {@link #when} (When order should be fulfilled.) - */ - public OrderWhenComponent getWhen() { - if (this.when == null) - if (Configuration.errorOnAutoCreate()) - throw new Error("Attempt to auto-create Order.when"); - else if (Configuration.doAutoCreate()) - this.when = new OrderWhenComponent(); // cc - return this.when; - } - - public boolean hasWhen() { - return this.when != null && !this.when.isEmpty(); - } - - /** - * @param value {@link #when} (When order should be fulfilled.) - */ - public Order setWhen(OrderWhenComponent value) { - this.when = value; - return this; - } - - /** - * @return {@link #detail} (What action is being ordered.) - */ - public List getDetail() { - if (this.detail == null) - this.detail = new ArrayList(); - return this.detail; - } - - public boolean hasDetail() { - if (this.detail == null) - return false; - for (Reference item : this.detail) - if (!item.isEmpty()) - return true; - return false; - } - - /** - * @return {@link #detail} (What action is being ordered.) - */ - // syntactic sugar - public Reference addDetail() { //3 - Reference t = new Reference(); - if (this.detail == null) - this.detail = new ArrayList(); - this.detail.add(t); - return t; - } - - // syntactic sugar - public Order addDetail(Reference t) { //3 - if (t == null) - return this; - if (this.detail == null) - this.detail = new ArrayList(); - this.detail.add(t); - return this; - } - - /** - * @return {@link #detail} (The actual objects that are the target of the reference. The reference library doesn't populate this, but you can use this to hold the resources if you resolvethemt. What action is being ordered.) - */ - public List getDetailTarget() { - if (this.detailTarget == null) - this.detailTarget = new ArrayList(); - return this.detailTarget; - } - - protected void listChildren(List childrenList) { - super.listChildren(childrenList); - childrenList.add(new Property("identifier", "Identifier", "Identifiers assigned to this order by the orderer or by the receiver.", 0, java.lang.Integer.MAX_VALUE, identifier)); - childrenList.add(new Property("date", "dateTime", "When the order was made.", 0, java.lang.Integer.MAX_VALUE, date)); - childrenList.add(new Property("subject", "Reference(Patient|Group|Device|Substance)", "Patient this order is about.", 0, java.lang.Integer.MAX_VALUE, subject)); - childrenList.add(new Property("source", "Reference(Practitioner|Organization)", "Who initiated the order.", 0, java.lang.Integer.MAX_VALUE, source)); - childrenList.add(new Property("target", "Reference(Organization|Device|Practitioner)", "Who is intended to fulfill the order.", 0, java.lang.Integer.MAX_VALUE, target)); - childrenList.add(new Property("reason[x]", "CodeableConcept|Reference(Any)", "Text - why the order was made.", 0, java.lang.Integer.MAX_VALUE, reason)); - childrenList.add(new Property("when", "", "When order should be fulfilled.", 0, java.lang.Integer.MAX_VALUE, when)); - childrenList.add(new Property("detail", "Reference(Any)", "What action is being ordered.", 0, java.lang.Integer.MAX_VALUE, detail)); - } - - @Override - public void setProperty(String name, Base value) throws FHIRException { - if (name.equals("identifier")) - this.getIdentifier().add(castToIdentifier(value)); - else if (name.equals("date")) - this.date = castToDateTime(value); // DateTimeType - else if (name.equals("subject")) - this.subject = castToReference(value); // Reference - else if (name.equals("source")) - this.source = castToReference(value); // Reference - else if (name.equals("target")) - this.target = castToReference(value); // Reference - else if (name.equals("reason[x]")) - this.reason = (Type) value; // Type - else if (name.equals("when")) - this.when = (OrderWhenComponent) value; // OrderWhenComponent - else if (name.equals("detail")) - this.getDetail().add(castToReference(value)); - else - super.setProperty(name, value); - } - - @Override - public Base addChild(String name) throws FHIRException { - if (name.equals("identifier")) { - return addIdentifier(); - } - else if (name.equals("date")) { - throw new FHIRException("Cannot call addChild on a primitive type Order.date"); - } - else if (name.equals("subject")) { - this.subject = new Reference(); - return this.subject; - } - else if (name.equals("source")) { - this.source = new Reference(); - return this.source; - } - else if (name.equals("target")) { - this.target = new Reference(); - return this.target; - } - else if (name.equals("reasonCodeableConcept")) { - this.reason = new CodeableConcept(); - return this.reason; - } - else if (name.equals("reasonReference")) { - this.reason = new Reference(); - return this.reason; - } - else if (name.equals("when")) { - this.when = new OrderWhenComponent(); - return this.when; - } - else if (name.equals("detail")) { - return addDetail(); - } - else - return super.addChild(name); - } - - public String fhirType() { - return "Order"; - - } - - public Order copy() { - Order dst = new Order(); - copyValues(dst); - if (identifier != null) { - dst.identifier = new ArrayList(); - for (Identifier i : identifier) - dst.identifier.add(i.copy()); - }; - dst.date = date == null ? null : date.copy(); - dst.subject = subject == null ? null : subject.copy(); - dst.source = source == null ? null : source.copy(); - dst.target = target == null ? null : target.copy(); - dst.reason = reason == null ? null : reason.copy(); - dst.when = when == null ? null : when.copy(); - if (detail != null) { - dst.detail = new ArrayList(); - for (Reference i : detail) - dst.detail.add(i.copy()); - }; - return dst; - } - - protected Order typedCopy() { - return copy(); - } - - @Override - public boolean equalsDeep(Base other) { - if (!super.equalsDeep(other)) - return false; - if (!(other instanceof Order)) - return false; - Order o = (Order) other; - return compareDeep(identifier, o.identifier, true) && compareDeep(date, o.date, true) && compareDeep(subject, o.subject, true) - && compareDeep(source, o.source, true) && compareDeep(target, o.target, true) && compareDeep(reason, o.reason, true) - && compareDeep(when, o.when, true) && compareDeep(detail, o.detail, true); - } - - @Override - public boolean equalsShallow(Base other) { - if (!super.equalsShallow(other)) - return false; - if (!(other instanceof Order)) - return false; - Order o = (Order) other; - return compareValues(date, o.date, true); - } - - public boolean isEmpty() { - return super.isEmpty() && (identifier == null || identifier.isEmpty()) && (date == null || date.isEmpty()) - && (subject == null || subject.isEmpty()) && (source == null || source.isEmpty()) && (target == null || target.isEmpty()) - && (reason == null || reason.isEmpty()) && (when == null || when.isEmpty()) && (detail == null || detail.isEmpty()) - ; - } - - @Override - public ResourceType getResourceType() { - return ResourceType.Order; - } - - @SearchParamDefinition(name="date", path="Order.date", description="When the order was made", type="date" ) - public static final String SP_DATE = "date"; - @SearchParamDefinition(name="identifier", path="Order.identifier", description="Instance id from source, target, and/or others", type="token" ) - public static final String SP_IDENTIFIER = "identifier"; - @SearchParamDefinition(name="subject", path="Order.subject", description="Patient this order is about", type="reference" ) - public static final String SP_SUBJECT = "subject"; - @SearchParamDefinition(name="patient", path="Order.subject", description="Patient this order is about", type="reference" ) - public static final String SP_PATIENT = "patient"; - @SearchParamDefinition(name="source", path="Order.source", description="Who initiated the order", type="reference" ) - public static final String SP_SOURCE = "source"; - @SearchParamDefinition(name="detail", path="Order.detail", description="What action is being ordered", type="reference" ) - public static final String SP_DETAIL = "detail"; - @SearchParamDefinition(name="when", path="Order.when.schedule", description="A formal schedule", type="date" ) - public static final String SP_WHEN = "when"; - @SearchParamDefinition(name="target", path="Order.target", description="Who is intended to fulfill the order", type="reference" ) - public static final String SP_TARGET = "target"; - @SearchParamDefinition(name="when_code", path="Order.when.code", description="Code specifies when request should be done. The code may simply be a priority code", type="token" ) - public static final String SP_WHENCODE = "when_code"; - -} - +package org.hl7.fhir.instance.model; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +*/ + +// Generated on Wed, Jul 13, 2016 05:32+1000 for FHIR v1.0.2 + +import ca.uhn.fhir.model.api.annotation.*; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.api.IBaseBackboneElement; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +/** + * A request to perform an action. + */ +@ResourceDef(name="Order", profile="http://hl7.org/fhir/Profile/Order") +public class Order extends DomainResource { + + @Block() + public static class OrderWhenComponent extends BackboneElement implements IBaseBackboneElement { + /** + * Code specifies when request should be done. The code may simply be a priority code. + */ + @Child(name = "code", type = {CodeableConcept.class}, order=1, min=0, max=1, modifier=false, summary=true) + @Description(shortDefinition="Code specifies when request should be done. The code may simply be a priority code", formalDefinition="Code specifies when request should be done. The code may simply be a priority code." ) + protected CodeableConcept code; + + /** + * A formal schedule. + */ + @Child(name = "schedule", type = {Timing.class}, order=2, min=0, max=1, modifier=false, summary=true) + @Description(shortDefinition="A formal schedule", formalDefinition="A formal schedule." ) + protected Timing schedule; + + private static final long serialVersionUID = 307115287L; + + /* + * Constructor + */ + public OrderWhenComponent() { + super(); + } + + /** + * @return {@link #code} (Code specifies when request should be done. The code may simply be a priority code.) + */ + public CodeableConcept getCode() { + if (this.code == null) + if (Configuration.errorOnAutoCreate()) + throw new Error("Attempt to auto-create OrderWhenComponent.code"); + else if (Configuration.doAutoCreate()) + this.code = new CodeableConcept(); // cc + return this.code; + } + + public boolean hasCode() { + return this.code != null && !this.code.isEmpty(); + } + + /** + * @param value {@link #code} (Code specifies when request should be done. The code may simply be a priority code.) + */ + public OrderWhenComponent setCode(CodeableConcept value) { + this.code = value; + return this; + } + + /** + * @return {@link #schedule} (A formal schedule.) + */ + public Timing getSchedule() { + if (this.schedule == null) + if (Configuration.errorOnAutoCreate()) + throw new Error("Attempt to auto-create OrderWhenComponent.schedule"); + else if (Configuration.doAutoCreate()) + this.schedule = new Timing(); // cc + return this.schedule; + } + + public boolean hasSchedule() { + return this.schedule != null && !this.schedule.isEmpty(); + } + + /** + * @param value {@link #schedule} (A formal schedule.) + */ + public OrderWhenComponent setSchedule(Timing value) { + this.schedule = value; + return this; + } + + protected void listChildren(List childrenList) { + super.listChildren(childrenList); + childrenList.add(new Property("code", "CodeableConcept", "Code specifies when request should be done. The code may simply be a priority code.", 0, java.lang.Integer.MAX_VALUE, code)); + childrenList.add(new Property("schedule", "Timing", "A formal schedule.", 0, java.lang.Integer.MAX_VALUE, schedule)); + } + + @Override + public void setProperty(String name, Base value) throws FHIRException { + if (name.equals("code")) + this.code = castToCodeableConcept(value); // CodeableConcept + else if (name.equals("schedule")) + this.schedule = castToTiming(value); // Timing + else + super.setProperty(name, value); + } + + @Override + public Base addChild(String name) throws FHIRException { + if (name.equals("code")) { + this.code = new CodeableConcept(); + return this.code; + } + else if (name.equals("schedule")) { + this.schedule = new Timing(); + return this.schedule; + } + else + return super.addChild(name); + } + + public OrderWhenComponent copy() { + OrderWhenComponent dst = new OrderWhenComponent(); + copyValues(dst); + dst.code = code == null ? null : code.copy(); + dst.schedule = schedule == null ? null : schedule.copy(); + return dst; + } + + @Override + public boolean equalsDeep(Base other) { + if (!super.equalsDeep(other)) + return false; + if (!(other instanceof OrderWhenComponent)) + return false; + OrderWhenComponent o = (OrderWhenComponent) other; + return compareDeep(code, o.code, true) && compareDeep(schedule, o.schedule, true); + } + + @Override + public boolean equalsShallow(Base other) { + if (!super.equalsShallow(other)) + return false; + if (!(other instanceof OrderWhenComponent)) + return false; + OrderWhenComponent o = (OrderWhenComponent) other; + return true; + } + + public boolean isEmpty() { + return super.isEmpty() && (code == null || code.isEmpty()) && (schedule == null || schedule.isEmpty()) + ; + } + + public String fhirType() { + return "Order.when"; + + } + + } + + /** + * Identifiers assigned to this order by the orderer or by the receiver. + */ + @Child(name = "identifier", type = {Identifier.class}, order=0, min=0, max=Child.MAX_UNLIMITED, modifier=false, summary=true) + @Description(shortDefinition="Identifiers assigned to this order by the orderer or by the receiver", formalDefinition="Identifiers assigned to this order by the orderer or by the receiver." ) + protected List identifier; + + /** + * When the order was made. + */ + @Child(name = "date", type = {DateTimeType.class}, order=1, min=0, max=1, modifier=false, summary=true) + @Description(shortDefinition="When the order was made", formalDefinition="When the order was made." ) + protected DateTimeType date; + + /** + * Patient this order is about. + */ + @Child(name = "subject", type = {Patient.class, Group.class, Device.class, Substance.class}, order=2, min=0, max=1, modifier=false, summary=true) + @Description(shortDefinition="Patient this order is about", formalDefinition="Patient this order is about." ) + protected Reference subject; + + /** + * The actual object that is the target of the reference (Patient this order is about.) + */ + protected Resource subjectTarget; + + /** + * Who initiated the order. + */ + @Child(name = "source", type = {Practitioner.class, Organization.class}, order=3, min=0, max=1, modifier=false, summary=true) + @Description(shortDefinition="Who initiated the order", formalDefinition="Who initiated the order." ) + protected Reference source; + + /** + * The actual object that is the target of the reference (Who initiated the order.) + */ + protected Resource sourceTarget; + + /** + * Who is intended to fulfill the order. + */ + @Child(name = "target", type = {Organization.class, Device.class, Practitioner.class}, order=4, min=0, max=1, modifier=false, summary=true) + @Description(shortDefinition="Who is intended to fulfill the order", formalDefinition="Who is intended to fulfill the order." ) + protected Reference target; + + /** + * The actual object that is the target of the reference (Who is intended to fulfill the order.) + */ + protected Resource targetTarget; + + /** + * Text - why the order was made. + */ + @Child(name = "reason", type = {CodeableConcept.class}, order=5, min=0, max=1, modifier=false, summary=true) + @Description(shortDefinition="Text - why the order was made", formalDefinition="Text - why the order was made." ) + protected Type reason; + + /** + * When order should be fulfilled. + */ + @Child(name = "when", type = {}, order=6, min=0, max=1, modifier=false, summary=true) + @Description(shortDefinition="When order should be fulfilled", formalDefinition="When order should be fulfilled." ) + protected OrderWhenComponent when; + + /** + * What action is being ordered. + */ + @Child(name = "detail", type = {}, order=7, min=1, max=Child.MAX_UNLIMITED, modifier=false, summary=true) + @Description(shortDefinition="What action is being ordered", formalDefinition="What action is being ordered." ) + protected List detail; + /** + * The actual objects that are the target of the reference (What action is being ordered.) + */ + protected List detailTarget; + + + private static final long serialVersionUID = -1392311096L; + + /* + * Constructor + */ + public Order() { + super(); + } + + /** + * @return {@link #identifier} (Identifiers assigned to this order by the orderer or by the receiver.) + */ + public List getIdentifier() { + if (this.identifier == null) + this.identifier = new ArrayList(); + return this.identifier; + } + + public boolean hasIdentifier() { + if (this.identifier == null) + return false; + for (Identifier item : this.identifier) + if (!item.isEmpty()) + return true; + return false; + } + + /** + * @return {@link #identifier} (Identifiers assigned to this order by the orderer or by the receiver.) + */ + // syntactic sugar + public Identifier addIdentifier() { //3 + Identifier t = new Identifier(); + if (this.identifier == null) + this.identifier = new ArrayList(); + this.identifier.add(t); + return t; + } + + // syntactic sugar + public Order addIdentifier(Identifier t) { //3 + if (t == null) + return this; + if (this.identifier == null) + this.identifier = new ArrayList(); + this.identifier.add(t); + return this; + } + + /** + * @return {@link #date} (When the order was made.). This is the underlying object with id, value and extensions. The accessor "getDate" gives direct access to the value + */ + public DateTimeType getDateElement() { + if (this.date == null) + if (Configuration.errorOnAutoCreate()) + throw new Error("Attempt to auto-create Order.date"); + else if (Configuration.doAutoCreate()) + this.date = new DateTimeType(); // bb + return this.date; + } + + public boolean hasDateElement() { + return this.date != null && !this.date.isEmpty(); + } + + public boolean hasDate() { + return this.date != null && !this.date.isEmpty(); + } + + /** + * @param value {@link #date} (When the order was made.). This is the underlying object with id, value and extensions. The accessor "getDate" gives direct access to the value + */ + public Order setDateElement(DateTimeType value) { + this.date = value; + return this; + } + + /** + * @return When the order was made. + */ + public Date getDate() { + return this.date == null ? null : this.date.getValue(); + } + + /** + * @param value When the order was made. + */ + public Order setDate(Date value) { + if (value == null) + this.date = null; + else { + if (this.date == null) + this.date = new DateTimeType(); + this.date.setValue(value); + } + return this; + } + + /** + * @return {@link #subject} (Patient this order is about.) + */ + public Reference getSubject() { + if (this.subject == null) + if (Configuration.errorOnAutoCreate()) + throw new Error("Attempt to auto-create Order.subject"); + else if (Configuration.doAutoCreate()) + this.subject = new Reference(); // cc + return this.subject; + } + + public boolean hasSubject() { + return this.subject != null && !this.subject.isEmpty(); + } + + /** + * @param value {@link #subject} (Patient this order is about.) + */ + public Order setSubject(Reference value) { + this.subject = value; + return this; + } + + /** + * @return {@link #subject} The actual object that is the target of the reference. The reference library doesn't populate this, but you can use it to hold the resource if you resolve it. (Patient this order is about.) + */ + public Resource getSubjectTarget() { + return this.subjectTarget; + } + + /** + * @param value {@link #subject} The actual object that is the target of the reference. The reference library doesn't use these, but you can use it to hold the resource if you resolve it. (Patient this order is about.) + */ + public Order setSubjectTarget(Resource value) { + this.subjectTarget = value; + return this; + } + + /** + * @return {@link #source} (Who initiated the order.) + */ + public Reference getSource() { + if (this.source == null) + if (Configuration.errorOnAutoCreate()) + throw new Error("Attempt to auto-create Order.source"); + else if (Configuration.doAutoCreate()) + this.source = new Reference(); // cc + return this.source; + } + + public boolean hasSource() { + return this.source != null && !this.source.isEmpty(); + } + + /** + * @param value {@link #source} (Who initiated the order.) + */ + public Order setSource(Reference value) { + this.source = value; + return this; + } + + /** + * @return {@link #source} The actual object that is the target of the reference. The reference library doesn't populate this, but you can use it to hold the resource if you resolve it. (Who initiated the order.) + */ + public Resource getSourceTarget() { + return this.sourceTarget; + } + + /** + * @param value {@link #source} The actual object that is the target of the reference. The reference library doesn't use these, but you can use it to hold the resource if you resolve it. (Who initiated the order.) + */ + public Order setSourceTarget(Resource value) { + this.sourceTarget = value; + return this; + } + + /** + * @return {@link #target} (Who is intended to fulfill the order.) + */ + public Reference getTarget() { + if (this.target == null) + if (Configuration.errorOnAutoCreate()) + throw new Error("Attempt to auto-create Order.target"); + else if (Configuration.doAutoCreate()) + this.target = new Reference(); // cc + return this.target; + } + + public boolean hasTarget() { + return this.target != null && !this.target.isEmpty(); + } + + /** + * @param value {@link #target} (Who is intended to fulfill the order.) + */ + public Order setTarget(Reference value) { + this.target = value; + return this; + } + + /** + * @return {@link #target} The actual object that is the target of the reference. The reference library doesn't populate this, but you can use it to hold the resource if you resolve it. (Who is intended to fulfill the order.) + */ + public Resource getTargetTarget() { + return this.targetTarget; + } + + /** + * @param value {@link #target} The actual object that is the target of the reference. The reference library doesn't use these, but you can use it to hold the resource if you resolve it. (Who is intended to fulfill the order.) + */ + public Order setTargetTarget(Resource value) { + this.targetTarget = value; + return this; + } + + /** + * @return {@link #reason} (Text - why the order was made.) + */ + public Type getReason() { + return this.reason; + } + + /** + * @return {@link #reason} (Text - why the order was made.) + */ + public CodeableConcept getReasonCodeableConcept() throws FHIRException { + if (!(this.reason instanceof CodeableConcept)) + throw new FHIRException("Type mismatch: the type CodeableConcept was expected, but "+this.reason.getClass().getName()+" was encountered"); + return (CodeableConcept) this.reason; + } + + public boolean hasReasonCodeableConcept() { + return this.reason instanceof CodeableConcept; + } + + /** + * @return {@link #reason} (Text - why the order was made.) + */ + public Reference getReasonReference() throws FHIRException { + if (!(this.reason instanceof Reference)) + throw new FHIRException("Type mismatch: the type Reference was expected, but "+this.reason.getClass().getName()+" was encountered"); + return (Reference) this.reason; + } + + public boolean hasReasonReference() { + return this.reason instanceof Reference; + } + + public boolean hasReason() { + return this.reason != null && !this.reason.isEmpty(); + } + + /** + * @param value {@link #reason} (Text - why the order was made.) + */ + public Order setReason(Type value) { + this.reason = value; + return this; + } + + /** + * @return {@link #when} (When order should be fulfilled.) + */ + public OrderWhenComponent getWhen() { + if (this.when == null) + if (Configuration.errorOnAutoCreate()) + throw new Error("Attempt to auto-create Order.when"); + else if (Configuration.doAutoCreate()) + this.when = new OrderWhenComponent(); // cc + return this.when; + } + + public boolean hasWhen() { + return this.when != null && !this.when.isEmpty(); + } + + /** + * @param value {@link #when} (When order should be fulfilled.) + */ + public Order setWhen(OrderWhenComponent value) { + this.when = value; + return this; + } + + /** + * @return {@link #detail} (What action is being ordered.) + */ + public List getDetail() { + if (this.detail == null) + this.detail = new ArrayList(); + return this.detail; + } + + public boolean hasDetail() { + if (this.detail == null) + return false; + for (Reference item : this.detail) + if (!item.isEmpty()) + return true; + return false; + } + + /** + * @return {@link #detail} (What action is being ordered.) + */ + // syntactic sugar + public Reference addDetail() { //3 + Reference t = new Reference(); + if (this.detail == null) + this.detail = new ArrayList(); + this.detail.add(t); + return t; + } + + // syntactic sugar + public Order addDetail(Reference t) { //3 + if (t == null) + return this; + if (this.detail == null) + this.detail = new ArrayList(); + this.detail.add(t); + return this; + } + + /** + * @return {@link #detail} (The actual objects that are the target of the reference. The reference library doesn't populate this, but you can use this to hold the resources if you resolvethemt. What action is being ordered.) + */ + public List getDetailTarget() { + if (this.detailTarget == null) + this.detailTarget = new ArrayList(); + return this.detailTarget; + } + + protected void listChildren(List childrenList) { + super.listChildren(childrenList); + childrenList.add(new Property("identifier", "Identifier", "Identifiers assigned to this order by the orderer or by the receiver.", 0, java.lang.Integer.MAX_VALUE, identifier)); + childrenList.add(new Property("date", "dateTime", "When the order was made.", 0, java.lang.Integer.MAX_VALUE, date)); + childrenList.add(new Property("subject", "Reference(Patient|Group|Device|Substance)", "Patient this order is about.", 0, java.lang.Integer.MAX_VALUE, subject)); + childrenList.add(new Property("source", "Reference(Practitioner|Organization)", "Who initiated the order.", 0, java.lang.Integer.MAX_VALUE, source)); + childrenList.add(new Property("target", "Reference(Organization|Device|Practitioner)", "Who is intended to fulfill the order.", 0, java.lang.Integer.MAX_VALUE, target)); + childrenList.add(new Property("reason[x]", "CodeableConcept|Reference(Any)", "Text - why the order was made.", 0, java.lang.Integer.MAX_VALUE, reason)); + childrenList.add(new Property("when", "", "When order should be fulfilled.", 0, java.lang.Integer.MAX_VALUE, when)); + childrenList.add(new Property("detail", "Reference(Any)", "What action is being ordered.", 0, java.lang.Integer.MAX_VALUE, detail)); + } + + @Override + public void setProperty(String name, Base value) throws FHIRException { + if (name.equals("identifier")) + this.getIdentifier().add(castToIdentifier(value)); + else if (name.equals("date")) + this.date = castToDateTime(value); // DateTimeType + else if (name.equals("subject")) + this.subject = castToReference(value); // Reference + else if (name.equals("source")) + this.source = castToReference(value); // Reference + else if (name.equals("target")) + this.target = castToReference(value); // Reference + else if (name.equals("reason[x]")) + this.reason = (Type) value; // Type + else if (name.equals("when")) + this.when = (OrderWhenComponent) value; // OrderWhenComponent + else if (name.equals("detail")) + this.getDetail().add(castToReference(value)); + else + super.setProperty(name, value); + } + + @Override + public Base addChild(String name) throws FHIRException { + if (name.equals("identifier")) { + return addIdentifier(); + } + else if (name.equals("date")) { + throw new FHIRException("Cannot call addChild on a primitive type Order.date"); + } + else if (name.equals("subject")) { + this.subject = new Reference(); + return this.subject; + } + else if (name.equals("source")) { + this.source = new Reference(); + return this.source; + } + else if (name.equals("target")) { + this.target = new Reference(); + return this.target; + } + else if (name.equals("reasonCodeableConcept")) { + this.reason = new CodeableConcept(); + return this.reason; + } + else if (name.equals("reasonReference")) { + this.reason = new Reference(); + return this.reason; + } + else if (name.equals("when")) { + this.when = new OrderWhenComponent(); + return this.when; + } + else if (name.equals("detail")) { + return addDetail(); + } + else + return super.addChild(name); + } + + public String fhirType() { + return "Order"; + + } + + public Order copy() { + Order dst = new Order(); + copyValues(dst); + if (identifier != null) { + dst.identifier = new ArrayList(); + for (Identifier i : identifier) + dst.identifier.add(i.copy()); + }; + dst.date = date == null ? null : date.copy(); + dst.subject = subject == null ? null : subject.copy(); + dst.source = source == null ? null : source.copy(); + dst.target = target == null ? null : target.copy(); + dst.reason = reason == null ? null : reason.copy(); + dst.when = when == null ? null : when.copy(); + if (detail != null) { + dst.detail = new ArrayList(); + for (Reference i : detail) + dst.detail.add(i.copy()); + }; + return dst; + } + + protected Order typedCopy() { + return copy(); + } + + @Override + public boolean equalsDeep(Base other) { + if (!super.equalsDeep(other)) + return false; + if (!(other instanceof Order)) + return false; + Order o = (Order) other; + return compareDeep(identifier, o.identifier, true) && compareDeep(date, o.date, true) && compareDeep(subject, o.subject, true) + && compareDeep(source, o.source, true) && compareDeep(target, o.target, true) && compareDeep(reason, o.reason, true) + && compareDeep(when, o.when, true) && compareDeep(detail, o.detail, true); + } + + @Override + public boolean equalsShallow(Base other) { + if (!super.equalsShallow(other)) + return false; + if (!(other instanceof Order)) + return false; + Order o = (Order) other; + return compareValues(date, o.date, true); + } + + public boolean isEmpty() { + return super.isEmpty() && (identifier == null || identifier.isEmpty()) && (date == null || date.isEmpty()) + && (subject == null || subject.isEmpty()) && (source == null || source.isEmpty()) && (target == null || target.isEmpty()) + && (reason == null || reason.isEmpty()) && (when == null || when.isEmpty()) && (detail == null || detail.isEmpty()) + ; + } + + @Override + public ResourceType getResourceType() { + return ResourceType.Order; + } + + @SearchParamDefinition(name="date", path="Order.date", description="When the order was made", type="date" ) + public static final String SP_DATE = "date"; + @SearchParamDefinition(name="identifier", path="Order.identifier", description="Instance id from source, target, and/or others", type="token" ) + public static final String SP_IDENTIFIER = "identifier"; + @SearchParamDefinition(name="subject", path="Order.subject", description="Patient this order is about", type="reference" ) + public static final String SP_SUBJECT = "subject"; + @SearchParamDefinition(name="patient", path="Order.subject", description="Patient this order is about", type="reference" ) + public static final String SP_PATIENT = "patient"; + @SearchParamDefinition(name="source", path="Order.source", description="Who initiated the order", type="reference" ) + public static final String SP_SOURCE = "source"; + @SearchParamDefinition(name="detail", path="Order.detail", description="What action is being ordered", type="reference" ) + public static final String SP_DETAIL = "detail"; + @SearchParamDefinition(name="when", path="Order.when.schedule", description="A formal schedule", type="date" ) + public static final String SP_WHEN = "when"; + @SearchParamDefinition(name="target", path="Order.target", description="Who is intended to fulfill the order", type="reference" ) + public static final String SP_TARGET = "target"; + @SearchParamDefinition(name="when_code", path="Order.when.code", description="Code specifies when request should be done. The code may simply be a priority code", type="token" ) + public static final String SP_WHENCODE = "when_code"; + +} + diff --git a/src/changes/changes.xml b/src/changes/changes.xml index e2fd6ff55cd..dea8e354a18 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -71,6 +71,11 @@ timezones where the date that could apply. This makes the search slightly more inclusive, which errs on the side of caution. + + A bug was fixed in the JPA server $expunge operation where a database connection + could sometimes be opened and not returned to the pool immediately, leading to + pool starvation if the operation was called many times in a row. +