In-memory matcher (#1116)
* Initial refactoring to move database matcher out into its own class * MAJOR REFACTOR: Pulled indexing code out of BaseHapiFhirDao into a new class ResourceIndexedSearchParams * Moved calculateHashes * Replaced @Bean definitions in BaseConfig.java with @ComponentScan Annotated bean classes with either @Service (if it's stateless) or @Component (if it's stateful). It doesn't really matter which annotation is used, but it's helpful to see at a glance whether a bean is stateful or stateless. * Move services out of BaseHapiFhirDao Moved services required by ResourceIndexedSearchParams out of BaseHapiFhirDao and into new classes called LogicalReferenceHelper, IdHelperService, MatchUrlService, and DaoProvider. Converted SearchBuilder into Prototype Bean Mark Spring components that depend on daos and entitymanagers with @Lazy so they aren't picked up by hapi-fhir-spring-boot-autoconfigure. * Added SubscriptionMatcherInMemory Moved static data out of BaseHapiFhirDao into ResourceMetaParams Moved translateMatchUrl methods out of BaseHapiFhirDao into MatchUrlService bean Simplified SubscriptionMatcherInMemory to not depend on entity or dao Turned all subscribers into prototype beans * Moved searchParam method out to mySearchParamProvider Also removed dao and contest parameters from of myMatchUrlService methods Moved code out of SearchBuilder into SearchParameterMap.clean() so it can be used by inMemoryMatcher Introduced a new composite subscription matcher that tries to match in memory and if it finds a parameter in the criteria it doesn't support, it falls back to the database matcher. * Added support for references Also fixed a small bug in SearchParameterMap that was missing the ";" after "_has" when creating a normalized query from search params. * Finished implementing all tests from FhirResourceDaoR4SearchNoFtTest * Make in-memory matcher configurable, disabled by default * Validate Subscription criteria when they're submitted Send HTTP 422 UnprocessableEntityException if the criteria fail validation. * fixed Sonar "Blocker" issues. * Don't reload the resource before sending it out Since we can always force a reload using restHookDetails.isDeliverLatestVersion * Added tests to cover Custom Search param. * Split ResourceIndexedSearchParam into separate state and service classes * Cleaned up SearchBuilder. Removed uses of myCallingDao as an injection mechanism. Left // FIXME KHS cookie crumbs to clean up * Reduced dependencies on BaseHapiFhirDao Removed methods from IDao interface that were used for injection * Updated change log
This commit is contained in:
parent
45a5db6fd8
commit
03ebcafdf5
|
@ -60,7 +60,7 @@ public interface IQueryParameterType extends Serializable {
|
||||||
public String getValueAsQueryToken(FhirContext theContext);
|
public String getValueAsQueryToken(FhirContext theContext);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will return any qualifier that should be appended to the parameter name (e.g ":exact")
|
* This method will return any qualifier that should be appended to the parameter name (e.g ":exact"). Returns null if none are present.
|
||||||
*/
|
*/
|
||||||
public String getQueryParameterQualifier();
|
public String getQueryParameterQualifier();
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,11 @@ public class TokenParam extends BaseParam /*implements IQueryParameterType*/ {
|
||||||
@Override
|
@Override
|
||||||
String doGetValueAsQueryToken(FhirContext theContext) {
|
String doGetValueAsQueryToken(FhirContext theContext) {
|
||||||
if (getSystem() != null) {
|
if (getSystem() != null) {
|
||||||
|
if (getValue() != null) {
|
||||||
return ParameterUtil.escape(StringUtils.defaultString(getSystem())) + '|' + ParameterUtil.escape(getValue());
|
return ParameterUtil.escape(StringUtils.defaultString(getSystem())) + '|' + ParameterUtil.escape(getValue());
|
||||||
|
} else {
|
||||||
|
return ParameterUtil.escape(StringUtils.defaultString(getSystem())) + '|';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ParameterUtil.escape(getValue());
|
return ParameterUtil.escape(getValue());
|
||||||
}
|
}
|
||||||
|
|
|
@ -670,7 +670,7 @@
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<runOrder>alphabetical</runOrder>
|
<runOrder>alphabetical</runOrder>
|
||||||
<argLine>@{argLine} -Dfile.encoding=UTF-8 -Xmx1024m</argLine>
|
<argLine>@{argLine} -Dfile.encoding=UTF-8 -Xmx20484M -Xss128M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=2048M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC</argLine>
|
||||||
<forkCount>0.6C</forkCount>
|
<forkCount>0.6C</forkCount>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
|
@ -1,5 +1,30 @@
|
||||||
package ca.uhn.fhir.jpa.config;
|
package ca.uhn.fhir.jpa.config;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.i18n.HapiLocalizer;
|
||||||
|
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
|
||||||
|
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||||
|
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
||||||
|
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl;
|
||||||
|
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowire;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.*;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.core.task.AsyncTaskExecutor;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||||
|
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||||
|
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
|
||||||
|
import org.springframework.scheduling.TaskScheduler;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||||
|
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
|
||||||
|
import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean;
|
||||||
|
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||||
|
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR JPA Server
|
* HAPI FHIR JPA Server
|
||||||
|
@ -20,46 +45,14 @@ package ca.uhn.fhir.jpa.config;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.i18n.HapiLocalizer;
|
|
||||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
|
||||||
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
|
|
||||||
import ca.uhn.fhir.jpa.search.*;
|
|
||||||
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
|
||||||
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl;
|
|
||||||
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;
|
|
||||||
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
|
|
||||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
|
|
||||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowire;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
|
||||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
|
||||||
import org.springframework.core.env.Environment;
|
|
||||||
import org.springframework.core.task.AsyncTaskExecutor;
|
|
||||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
|
||||||
import org.springframework.orm.hibernate5.HibernateExceptionTranslator;
|
|
||||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
|
||||||
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
|
|
||||||
import org.springframework.scheduling.TaskScheduler;
|
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
|
||||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
|
||||||
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
|
|
||||||
import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean;
|
|
||||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data")
|
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data")
|
||||||
|
@ComponentScan(basePackages = "ca.uhn.fhir.jpa", excludeFilters={
|
||||||
|
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=BaseConfig.class),
|
||||||
|
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=WebSocketConfigurer.class)})
|
||||||
|
|
||||||
public abstract class BaseConfig implements SchedulingConfigurer {
|
public abstract class BaseConfig implements SchedulingConfigurer {
|
||||||
|
|
||||||
public static final String TASK_EXECUTOR_NAME = "hapiJpaTaskExecutor";
|
public static final String TASK_EXECUTOR_NAME = "hapiJpaTaskExecutor";
|
||||||
|
@ -67,11 +60,6 @@ public abstract class BaseConfig implements SchedulingConfigurer {
|
||||||
@Autowired
|
@Autowired
|
||||||
protected Environment myEnv;
|
protected Environment myEnv;
|
||||||
|
|
||||||
@Bean(name = "myDaoRegistry")
|
|
||||||
public DaoRegistry daoRegistry() {
|
|
||||||
return new DaoRegistry();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureTasks(@Nonnull ScheduledTaskRegistrar theTaskRegistrar) {
|
public void configureTasks(@Nonnull ScheduledTaskRegistrar theTaskRegistrar) {
|
||||||
theTaskRegistrar.setTaskScheduler(taskScheduler());
|
theTaskRegistrar.setTaskScheduler(taskScheduler());
|
||||||
|
@ -95,21 +83,6 @@ public abstract class BaseConfig implements SchedulingConfigurer {
|
||||||
|
|
||||||
public abstract FhirContext fhirContext();
|
public abstract FhirContext fhirContext();
|
||||||
|
|
||||||
@Bean
|
|
||||||
public ICacheWarmingSvc cacheWarmingSvc() {
|
|
||||||
return new CacheWarmingSvcImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public HibernateExceptionTranslator hibernateExceptionTranslator() {
|
|
||||||
return new HibernateExceptionTranslator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public HibernateJpaDialect hibernateJpaDialectInstance() {
|
|
||||||
return new HibernateJpaDialect();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean()
|
@Bean()
|
||||||
public ScheduledExecutorFactoryBean scheduledExecutorService() {
|
public ScheduledExecutorFactoryBean scheduledExecutorService() {
|
||||||
ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();
|
ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();
|
||||||
|
@ -124,44 +97,6 @@ public abstract class BaseConfig implements SchedulingConfigurer {
|
||||||
return new SubscriptionTriggeringProvider();
|
return new SubscriptionTriggeringProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(autowire = Autowire.BY_TYPE, name = "mySearchCoordinatorSvc")
|
|
||||||
public ISearchCoordinatorSvc searchCoordinatorSvc() {
|
|
||||||
return new SearchCoordinatorSvcImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public ISearchParamPresenceSvc searchParamPresenceSvc() {
|
|
||||||
return new SearchParamPresenceSvcImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(autowire = Autowire.BY_TYPE)
|
|
||||||
public IStaleSearchDeletingSvc staleSearchDeletingSvc() {
|
|
||||||
return new StaleSearchDeletingSvcImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Note: If you're going to use this, you need to provide a bean
|
|
||||||
* of type {@link ca.uhn.fhir.jpa.subscription.email.IEmailSender}
|
|
||||||
* in your own Spring config
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@Lazy
|
|
||||||
public SubscriptionEmailInterceptor subscriptionEmailInterceptor() {
|
|
||||||
return new SubscriptionEmailInterceptor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Lazy
|
|
||||||
public SubscriptionRestHookInterceptor subscriptionRestHookInterceptor() {
|
|
||||||
return new SubscriptionRestHookInterceptor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Lazy
|
|
||||||
public SubscriptionWebsocketInterceptor subscriptionWebsocketInterceptor() {
|
|
||||||
return new SubscriptionWebsocketInterceptor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean()
|
@Bean()
|
||||||
public TaskScheduler taskScheduler() {
|
public TaskScheduler taskScheduler() {
|
||||||
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();
|
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();
|
||||||
|
@ -192,13 +127,4 @@ public abstract class BaseConfig implements SchedulingConfigurer {
|
||||||
private static HibernateJpaDialect hibernateJpaDialect(HapiLocalizer theLocalizer) {
|
private static HibernateJpaDialect hibernateJpaDialect(HapiLocalizer theLocalizer) {
|
||||||
return new HapiFhirHibernateJpaDialect(theLocalizer);
|
return new HapiFhirHibernateJpaDialect(theLocalizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This lets the "@Value" fields reference properties from the properties file
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,127 +1,11 @@
|
||||||
package ca.uhn.fhir.jpa.dao;
|
package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
|
import ca.uhn.fhir.context.*;
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.trim;
|
|
||||||
|
|
||||||
import java.io.CharArrayWriter;
|
|
||||||
import java.text.Normalizer;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.persistence.NoResultException;
|
|
||||||
import javax.persistence.PersistenceContext;
|
|
||||||
import javax.persistence.PersistenceContextType;
|
|
||||||
import javax.persistence.Tuple;
|
|
||||||
import javax.persistence.TypedQuery;
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.CriteriaQuery;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import javax.persistence.criteria.Root;
|
|
||||||
import javax.xml.stream.events.Characters;
|
|
||||||
import javax.xml.stream.events.XMLEvent;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.data.*;
|
import ca.uhn.fhir.jpa.dao.data.*;
|
||||||
import com.google.common.collect.Lists;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
|
||||||
import org.apache.commons.lang3.Validate;
|
|
||||||
import org.apache.http.NameValuePair;
|
|
||||||
import org.apache.http.client.utils.URLEncodedUtils;
|
|
||||||
import org.hibernate.Session;
|
|
||||||
import org.hibernate.internal.SessionImpl;
|
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBase;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IDomainResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
|
||||||
import org.hl7.fhir.r4.model.BaseResource;
|
|
||||||
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.ApplicationContextAware;
|
|
||||||
import org.springframework.data.domain.PageRequest;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.data.domain.Slice;
|
|
||||||
import org.springframework.data.domain.SliceImpl;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.base.Charsets;
|
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import com.google.common.hash.HashFunction;
|
|
||||||
import com.google.common.hash.Hashing;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
|
||||||
import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
|
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.IndexingSupport;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.ResourceIndexedSearchParams;
|
import ca.uhn.fhir.jpa.dao.index.ResourceIndexedSearchParams;
|
||||||
import ca.uhn.fhir.jpa.entity.BaseHasResource;
|
import ca.uhn.fhir.jpa.dao.index.SearchParamExtractorService;
|
||||||
import ca.uhn.fhir.jpa.entity.BaseTag;
|
import ca.uhn.fhir.jpa.entity.*;
|
||||||
import ca.uhn.fhir.jpa.entity.ForcedId;
|
|
||||||
import ca.uhn.fhir.jpa.entity.IBaseResourceEntity;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceHistoryTag;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedCompositeStringUnique;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceLink;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTag;
|
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
|
||||||
import ca.uhn.fhir.jpa.entity.SearchInclude;
|
|
||||||
import ca.uhn.fhir.jpa.entity.SearchParamPresent;
|
|
||||||
import ca.uhn.fhir.jpa.entity.SearchResult;
|
|
||||||
import ca.uhn.fhir.jpa.entity.SearchStatusEnum;
|
|
||||||
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
|
||||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TagDefinition;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TermConceptMap;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TermConceptMapGroup;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TermConceptProperty;
|
|
||||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||||
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
||||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||||
|
@ -130,8 +14,6 @@ import ca.uhn.fhir.jpa.util.DeleteConflict;
|
||||||
import ca.uhn.fhir.jpa.util.ExpungeOptions;
|
import ca.uhn.fhir.jpa.util.ExpungeOptions;
|
||||||
import ca.uhn.fhir.jpa.util.ExpungeOutcome;
|
import ca.uhn.fhir.jpa.util.ExpungeOutcome;
|
||||||
import ca.uhn.fhir.jpa.util.JpaConstants;
|
import ca.uhn.fhir.jpa.util.JpaConstants;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterAnd;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||||
import ca.uhn.fhir.model.api.Tag;
|
import ca.uhn.fhir.model.api.Tag;
|
||||||
|
@ -146,25 +28,11 @@ import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import ca.uhn.fhir.parser.IParser;
|
import ca.uhn.fhir.parser.IParser;
|
||||||
import ca.uhn.fhir.parser.LenientErrorHandler;
|
import ca.uhn.fhir.parser.LenientErrorHandler;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
import ca.uhn.fhir.rest.server.exceptions.*;
|
||||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
|
||||||
import ca.uhn.fhir.rest.param.StringAndListParam;
|
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
|
||||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
|
||||||
import ca.uhn.fhir.rest.param.UriAndListParam;
|
|
||||||
import ca.uhn.fhir.rest.param.UriParam;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
|
||||||
|
@ -173,6 +41,45 @@ import ca.uhn.fhir.util.CoverageIgnore;
|
||||||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||||
import ca.uhn.fhir.util.StopWatch;
|
import ca.uhn.fhir.util.StopWatch;
|
||||||
import ca.uhn.fhir.util.XmlUtil;
|
import ca.uhn.fhir.util.XmlUtil;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import com.google.common.hash.HashFunction;
|
||||||
|
import com.google.common.hash.Hashing;
|
||||||
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.internal.SessionImpl;
|
||||||
|
import org.hl7.fhir.instance.model.api.*;
|
||||||
|
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Slice;
|
||||||
|
import org.springframework.data.domain.SliceImpl;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
|
import javax.persistence.criteria.Predicate;
|
||||||
|
import javax.persistence.criteria.Root;
|
||||||
|
import javax.xml.stream.events.Characters;
|
||||||
|
import javax.xml.stream.events.XMLEvent;
|
||||||
|
import java.io.CharArrayWriter;
|
||||||
|
import java.text.Normalizer;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.*;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
|
@ -196,7 +103,7 @@ import ca.uhn.fhir.util.XmlUtil;
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
@Repository
|
@Repository
|
||||||
public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao, ApplicationContextAware, IndexingSupport {
|
public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao, ApplicationContextAware {
|
||||||
|
|
||||||
public static final long INDEX_STATUS_INDEXED = 1L;
|
public static final long INDEX_STATUS_INDEXED = 1L;
|
||||||
public static final long INDEX_STATUS_INDEXING_FAILED = 2L;
|
public static final long INDEX_STATUS_INDEXING_FAILED = 2L;
|
||||||
|
@ -205,46 +112,17 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
public static final String OO_SEVERITY_INFO = "information";
|
public static final String OO_SEVERITY_INFO = "information";
|
||||||
public static final String OO_SEVERITY_WARN = "warning";
|
public static final String OO_SEVERITY_WARN = "warning";
|
||||||
public static final String UCUM_NS = "http://unitsofmeasure.org";
|
public static final String UCUM_NS = "http://unitsofmeasure.org";
|
||||||
static final Set<String> EXCLUDE_ELEMENTS_IN_ENCODED;
|
|
||||||
/**
|
|
||||||
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
|
|
||||||
*/
|
|
||||||
static final Map<String, Class<? extends IQueryParameterAnd<?>>> RESOURCE_META_AND_PARAMS;
|
|
||||||
/**
|
|
||||||
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
|
|
||||||
*/
|
|
||||||
static final Map<String, Class<? extends IQueryParameterType>> RESOURCE_META_PARAMS;
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class);
|
||||||
private static final Map<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<FhirVersionEnum, FhirContext>();
|
private static final Map<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<FhirVersionEnum, FhirContext>();
|
||||||
private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
|
private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
|
||||||
private static boolean ourValidationDisabledForUnitTest;
|
private static boolean ourValidationDisabledForUnitTest;
|
||||||
private static boolean ourDisableIncrementOnUpdateForUnitTest = false;
|
private static boolean ourDisableIncrementOnUpdateForUnitTest = false;
|
||||||
|
|
||||||
static {
|
|
||||||
Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>();
|
|
||||||
Map<String, Class<? extends IQueryParameterAnd<?>>> resourceMetaAndParams = new HashMap<String, Class<? extends IQueryParameterAnd<?>>>();
|
|
||||||
resourceMetaParams.put(BaseResource.SP_RES_ID, StringParam.class);
|
|
||||||
resourceMetaAndParams.put(BaseResource.SP_RES_ID, StringAndListParam.class);
|
|
||||||
resourceMetaParams.put(BaseResource.SP_RES_LANGUAGE, StringParam.class);
|
|
||||||
resourceMetaAndParams.put(BaseResource.SP_RES_LANGUAGE, StringAndListParam.class);
|
|
||||||
resourceMetaParams.put(Constants.PARAM_TAG, TokenParam.class);
|
|
||||||
resourceMetaAndParams.put(Constants.PARAM_TAG, TokenAndListParam.class);
|
|
||||||
resourceMetaParams.put(Constants.PARAM_PROFILE, UriParam.class);
|
|
||||||
resourceMetaAndParams.put(Constants.PARAM_PROFILE, UriAndListParam.class);
|
|
||||||
resourceMetaParams.put(Constants.PARAM_SECURITY, TokenParam.class);
|
|
||||||
resourceMetaAndParams.put(Constants.PARAM_SECURITY, TokenAndListParam.class);
|
|
||||||
RESOURCE_META_PARAMS = Collections.unmodifiableMap(resourceMetaParams);
|
|
||||||
RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams);
|
|
||||||
|
|
||||||
HashSet<String> excludeElementsInEncoded = new HashSet<String>();
|
|
||||||
excludeElementsInEncoded.add("id");
|
|
||||||
excludeElementsInEncoded.add("*.meta");
|
|
||||||
EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||||
protected EntityManager myEntityManager;
|
protected EntityManager myEntityManager;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
protected IdHelperService myIdHelperService;
|
||||||
|
@Autowired
|
||||||
protected IForcedIdDao myForcedIdDao;
|
protected IForcedIdDao myForcedIdDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
protected ISearchResultDao mySearchResultDao;
|
protected ISearchResultDao mySearchResultDao;
|
||||||
|
@ -294,27 +172,34 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISearchParamPresenceSvc mySearchParamPresenceSvc;
|
private ISearchParamPresenceSvc mySearchParamPresenceSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISearchParamRegistry mySearchParamRegistry;
|
protected ISearchParamRegistry mySearchParamRegistry;
|
||||||
//@Autowired
|
//@Autowired
|
||||||
//private ISearchResultDao mySearchResultDao;
|
//private ISearchResultDao mySearchResultDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
|
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
|
||||||
|
@Autowired
|
||||||
|
private BeanFactory beanFactory;
|
||||||
|
@Autowired
|
||||||
|
private DaoRegistry myDaoRegistry;
|
||||||
|
@Autowired
|
||||||
|
private MatchUrlService myMatchUrlService;
|
||||||
|
@Autowired
|
||||||
|
private SearchParamExtractorService mySearchParamExtractorService;
|
||||||
|
|
||||||
private ApplicationContext myApplicationContext;
|
private ApplicationContext myApplicationContext;
|
||||||
private Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> myResourceTypeToDao;
|
|
||||||
|
|
||||||
public static void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
|
public static void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
|
||||||
if (theRequestDetails != null) {
|
if (theRequestDetails != null) {
|
||||||
theRequestDetails.getUserData().remove(PROCESSING_SUB_REQUEST);
|
theRequestDetails.getUserData().remove(PROCESSING_SUB_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the newly created forced ID. If the entity already had a forced ID, or if
|
* Returns the newly created forced ID. If the entity already had a forced ID, or if
|
||||||
* none was created, returns null.
|
* none was created, returns null.
|
||||||
*/
|
*/
|
||||||
protected ForcedId createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId, boolean theCreateForPureNumericIds) {
|
protected ForcedId createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId, boolean theCreateForPureNumericIds) {
|
||||||
if (theId.isEmpty() == false && theId.hasIdPart() && theEntity.getForcedId() == null) {
|
if (theId.isEmpty() == false && theId.hasIdPart() && theEntity.getForcedId() == null) {
|
||||||
if (!theCreateForPureNumericIds && isValidPid(theId)) {
|
if (!theCreateForPureNumericIds && IdHelperService.isValidPid(theId)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,7 +423,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
ForcedId forcedId = resource.getForcedId();
|
ForcedId forcedId = resource.getForcedId();
|
||||||
resource.setForcedId(null);
|
resource.setForcedId(null);
|
||||||
myResourceTableDao.saveAndFlush(resource);
|
myResourceTableDao.saveAndFlush(resource);
|
||||||
myForcedIdDao.delete(forcedId);
|
myIdHelperService.delete(forcedId);
|
||||||
}
|
}
|
||||||
|
|
||||||
myResourceTableDao.delete(resource);
|
myResourceTableDao.delete(resource);
|
||||||
|
@ -656,7 +541,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
if (theResourceName != null) {
|
if (theResourceName != null) {
|
||||||
Predicate typePredicate = builder.equal(from.get("myResourceType"), theResourceName);
|
Predicate typePredicate = builder.equal(from.get("myResourceType"), theResourceName);
|
||||||
if (theResourceId != null) {
|
if (theResourceId != null) {
|
||||||
cq.where(typePredicate, builder.equal(from.get("myResourceId"), translateForcedIdToPid(theResourceName, theResourceId.getIdPart())));
|
cq.where(typePredicate, builder.equal(from.get("myResourceId"), myIdHelperService.translateForcedIdToPid(theResourceName, theResourceId.getIdPart())));
|
||||||
} else {
|
} else {
|
||||||
cq.where(typePredicate);
|
cq.where(typePredicate);
|
||||||
}
|
}
|
||||||
|
@ -688,8 +573,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected DaoConfig getConfig() {
|
||||||
public DaoConfig getConfig() {
|
|
||||||
return myConfig;
|
return myConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -719,51 +603,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType) {
|
public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType) {
|
||||||
Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> resourceTypeToDao = getDaos();
|
return myDaoRegistry.getResourceDaoIfExists(theType);
|
||||||
IFhirResourceDao<R> dao = (IFhirResourceDao<R>) resourceTypeToDao.get(theType);
|
|
||||||
return dao;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> getDaos() {
|
protected IFhirResourceDao<?> getDaoOrThrowException(Class<? extends IBaseResource> theClass) {
|
||||||
if (myResourceTypeToDao == null) {
|
return myDaoRegistry.getDaoOrThrowException(theClass);
|
||||||
Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> resourceTypeToDao = new HashMap<>();
|
|
||||||
|
|
||||||
Map<String, IFhirResourceDao> daos = myApplicationContext.getBeansOfType(IFhirResourceDao.class, false, false);
|
|
||||||
|
|
||||||
String[] beanNames = myApplicationContext.getBeanNamesForType(IFhirResourceDao.class);
|
|
||||||
|
|
||||||
for (IFhirResourceDao<?> next : daos.values()) {
|
|
||||||
resourceTypeToDao.put(next.getResourceType(), next);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this instanceof IFhirResourceDao<?>) {
|
|
||||||
IFhirResourceDao<?> thiz = (IFhirResourceDao<?>) this;
|
|
||||||
resourceTypeToDao.put(thiz.getResourceType(), thiz);
|
|
||||||
}
|
|
||||||
|
|
||||||
myResourceTypeToDao = resourceTypeToDao;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Collections.unmodifiableMap(myResourceTypeToDao);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IResourceIndexedCompositeStringUniqueDao getResourceIndexedCompositeStringUniqueDao() {
|
|
||||||
return myResourceIndexedCompositeStringUniqueDao;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) {
|
|
||||||
Map<String, RuntimeSearchParam> params = mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName());
|
|
||||||
return params.get(theParamName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
|
|
||||||
return mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName()).values();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TagDefinition getTagOrNull(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
|
protected TagDefinition getTagOrNull(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
|
||||||
|
@ -901,25 +747,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
theProvider.setSearchCoordinatorSvc(mySearchCoordinatorSvc);
|
theProvider.setSearchCoordinatorSvc(mySearchCoordinatorSvc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLogicalReference(IIdType theId) {
|
public boolean isLogicalReference(IIdType theId) {
|
||||||
Set<String> treatReferencesAsLogical = myConfig.getTreatReferencesAsLogical();
|
return LogicalReferenceHelper.isLogicalReference(myConfig, theId);
|
||||||
if (treatReferencesAsLogical != null) {
|
|
||||||
for (String nextLogicalRef : treatReferencesAsLogical) {
|
|
||||||
nextLogicalRef = trim(nextLogicalRef);
|
|
||||||
if (nextLogicalRef.charAt(nextLogicalRef.length() - 1) == '*') {
|
|
||||||
if (theId.getValue().startsWith(nextLogicalRef.substring(0, nextLogicalRef.length() - 1))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (theId.getValue().equals(nextLogicalRef)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void markRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
|
public static void markRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
|
||||||
|
@ -930,10 +759,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SearchBuilder newSearchBuilder() {
|
public SearchBuilder newSearchBuilder() {
|
||||||
SearchBuilder builder = new SearchBuilder(
|
return beanFactory.getBean(SearchBuilder.class, this);
|
||||||
getContext(), myEntityManager, myFulltextSearchSvc, this, myResourceIndexedSearchParamUriDao,
|
|
||||||
myForcedIdDao, myTerminologySvc, mySerarchParamRegistry, myResourceTagDao, myResourceViewDao);
|
|
||||||
return builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) {
|
public void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) {
|
||||||
|
@ -970,7 +796,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
return retVal.toString();
|
return retVal.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void populateFullTextFields(final IBaseResource theResource, ResourceTable theEntity) {
|
public void populateFullTextFields(final IBaseResource theResource, ResourceTable theEntity) {
|
||||||
if (theEntity.getDeleted() != null) {
|
if (theEntity.getDeleted() != null) {
|
||||||
theEntity.setNarrativeTextParsedIntoWords(null);
|
theEntity.setNarrativeTextParsedIntoWords(null);
|
||||||
|
@ -1015,7 +840,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
if (theEntity.getDeleted() == null) {
|
if (theEntity.getDeleted() == null) {
|
||||||
|
|
||||||
encoding = myConfig.getResourceEncoding();
|
encoding = myConfig.getResourceEncoding();
|
||||||
Set<String> excludeElements = EXCLUDE_ELEMENTS_IN_ENCODED;
|
Set<String> excludeElements = ResourceMetaParams.EXCLUDE_ELEMENTS_IN_ENCODED;
|
||||||
theEntity.setFhirVersion(myContext.getVersion().getVersion());
|
theEntity.setFhirVersion(myContext.getVersion().getVersion());
|
||||||
|
|
||||||
bytes = encodeResource(theResource, encoding, excludeElements, myContext);
|
bytes = encodeResource(theResource, encoding, excludeElements, myContext);
|
||||||
|
@ -1256,25 +1081,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public <R extends IBaseResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType) {
|
|
||||||
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType);
|
|
||||||
|
|
||||||
SearchParameterMap paramMap = translateMatchUrl(this, myContext, theMatchUrl, resourceDef);
|
|
||||||
paramMap.setLoadSynchronous(true);
|
|
||||||
|
|
||||||
if (paramMap.isEmpty() && paramMap.getLastUpdated() == null) {
|
|
||||||
throw new InvalidRequestException("Invalid match URL[" + theMatchUrl + "] - URL has no search parameters");
|
|
||||||
}
|
|
||||||
|
|
||||||
IFhirResourceDao<R> dao = getDao(theResourceType);
|
|
||||||
if (dao == null) {
|
|
||||||
throw new InternalErrorException("No DAO for resource type: " + theResourceType.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
return dao.searchForIds(paramMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
@CoverageIgnore
|
@CoverageIgnore
|
||||||
public BaseHasResource readEntity(IIdType theValueId) {
|
public BaseHasResource readEntity(IIdType theValueId) {
|
||||||
throw new NotImplementedException("");
|
throw new NotImplementedException("");
|
||||||
|
@ -1343,11 +1149,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void startClearCaches() {
|
|
||||||
myResourceTypeToDao = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpungeOutcome toExpungeOutcome(ExpungeOptions theExpungeOptions, AtomicInteger theRemainingCount) {
|
private ExpungeOutcome toExpungeOutcome(ExpungeOptions theExpungeOptions, AtomicInteger theRemainingCount) {
|
||||||
return new ExpungeOutcome()
|
return new ExpungeOutcome()
|
||||||
.setDeletedCount(theExpungeOptions.getLimit() - theRemainingCount.get());
|
.setDeletedCount(theExpungeOptions.getLimit() - theRemainingCount.get());
|
||||||
|
@ -1461,7 +1262,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toResourceName(Class<? extends IBaseResource> theResourceType) {
|
public String toResourceName(Class<? extends IBaseResource> theResourceType) {
|
||||||
return myContext.getResourceDefinition(theResourceType).getName();
|
return myContext.getResourceDefinition(theResourceType).getName();
|
||||||
}
|
}
|
||||||
|
@ -1475,16 +1275,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
return new SliceImpl<>(Collections.singletonList(theVersion.getId()));
|
return new SliceImpl<>(Collections.singletonList(theVersion.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long translateForcedIdToPid(String theResourceName, String theResourceId) {
|
|
||||||
return translateForcedIdToPids(getConfig(), new IdDt(theResourceName, theResourceId), myForcedIdDao).get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<Long> translateForcedIdToPids(IIdType theId) {
|
|
||||||
return translateForcedIdToPids(getConfig(), theId, myForcedIdDao);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected ResourceTable updateEntity(RequestDetails theRequest, final IBaseResource theResource, ResourceTable
|
protected ResourceTable updateEntity(RequestDetails theRequest, final IBaseResource theResource, ResourceTable
|
||||||
theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
|
theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
|
||||||
|
@ -1516,14 +1306,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
theEntity.setPublished(theUpdateTime);
|
theEntity.setPublished(theUpdateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceIndexedSearchParams existingParams = new ResourceIndexedSearchParams(this, theEntity);
|
ResourceIndexedSearchParams existingParams = new ResourceIndexedSearchParams(theEntity);
|
||||||
|
|
||||||
ResourceIndexedSearchParams newParams = null;
|
ResourceIndexedSearchParams newParams = null;
|
||||||
|
|
||||||
EncodedResource changed;
|
EncodedResource changed;
|
||||||
if (theDeletedTimestampOrNull != null) {
|
if (theDeletedTimestampOrNull != null) {
|
||||||
|
|
||||||
newParams = new ResourceIndexedSearchParams(this);
|
newParams = new ResourceIndexedSearchParams();
|
||||||
|
|
||||||
theEntity.setDeleted(theDeletedTimestampOrNull);
|
theEntity.setDeleted(theDeletedTimestampOrNull);
|
||||||
theEntity.setUpdated(theDeletedTimestampOrNull);
|
theEntity.setUpdated(theDeletedTimestampOrNull);
|
||||||
|
@ -1539,7 +1329,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
|
|
||||||
if (thePerformIndexing) {
|
if (thePerformIndexing) {
|
||||||
|
|
||||||
newParams = new ResourceIndexedSearchParams(this, theUpdateTime, theEntity, theResource, existingParams);
|
newParams = new ResourceIndexedSearchParams();
|
||||||
|
mySearchParamExtractorService.populateFromResource(newParams, this, theUpdateTime, theEntity, theResource, existingParams);
|
||||||
|
|
||||||
changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true);
|
changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true);
|
||||||
|
|
||||||
|
@ -1550,7 +1341,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
theEntity.setLanguage(((IAnyResource) theResource).getLanguageElement().getValue());
|
theEntity.setLanguage(((IAnyResource) theResource).getLanguageElement().getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
newParams.setParams(theEntity);
|
newParams.setParamsOn(theEntity);
|
||||||
theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
|
theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
|
||||||
populateFullTextFields(theResource, theEntity);
|
populateFullTextFields(theResource, theEntity);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1625,7 +1416,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
*/
|
*/
|
||||||
if (thePerformIndexing) {
|
if (thePerformIndexing) {
|
||||||
Map<String, Boolean> presentSearchParams = new HashMap<>();
|
Map<String, Boolean> presentSearchParams = new HashMap<>();
|
||||||
// TODO KHS null check?
|
|
||||||
for (String nextKey : newParams.getPopulatedResourceLinkParameters()) {
|
for (String nextKey : newParams.getPopulatedResourceLinkParameters()) {
|
||||||
presentSearchParams.put(nextKey, Boolean.TRUE);
|
presentSearchParams.put(nextKey, Boolean.TRUE);
|
||||||
}
|
}
|
||||||
|
@ -1644,8 +1434,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
* Indexing
|
* Indexing
|
||||||
*/
|
*/
|
||||||
if (thePerformIndexing) {
|
if (thePerformIndexing) {
|
||||||
newParams.removeCommon(theEntity, existingParams);
|
mySearchParamExtractorService.removeCommon(newParams, theEntity, existingParams);
|
||||||
|
|
||||||
} // if thePerformIndexing
|
} // if thePerformIndexing
|
||||||
|
|
||||||
if (theResource != null) {
|
if (theResource != null) {
|
||||||
|
@ -1884,44 +1673,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean isValidPid(IIdType theId) {
|
|
||||||
if (theId == null || theId.getIdPart() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String idPart = theId.getIdPart();
|
|
||||||
for (int i = 0; i < idPart.length(); i++) {
|
|
||||||
char nextChar = idPart.charAt(i);
|
|
||||||
if (nextChar < '0' || nextChar > '9') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@CoverageIgnore
|
|
||||||
protected static IQueryParameterAnd<?> newInstanceAnd(String chain) {
|
|
||||||
IQueryParameterAnd<?> type;
|
|
||||||
Class<? extends IQueryParameterAnd<?>> clazz = RESOURCE_META_AND_PARAMS.get(chain);
|
|
||||||
try {
|
|
||||||
type = clazz.newInstance();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new InternalErrorException("Failure creating instance of " + clazz, e);
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@CoverageIgnore
|
|
||||||
protected static IQueryParameterType newInstanceType(String chain) {
|
|
||||||
IQueryParameterType type;
|
|
||||||
Class<? extends IQueryParameterType> clazz = RESOURCE_META_PARAMS.get(chain);
|
|
||||||
try {
|
|
||||||
type = clazz.newInstance();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new InternalErrorException("Failure creating instance of " + clazz, e);
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String normalizeString(String theString) {
|
public static String normalizeString(String theString) {
|
||||||
CharArrayWriter outBuffer = new CharArrayWriter(theString.length());
|
CharArrayWriter outBuffer = new CharArrayWriter(theString.length());
|
||||||
|
|
||||||
|
@ -2004,140 +1755,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Long translateForcedIdToPid(DaoConfig theDaoConfig, String theResourceName, String theResourceId, IForcedIdDao
|
|
||||||
theForcedIdDao) {
|
|
||||||
return translateForcedIdToPids(theDaoConfig, new IdDt(theResourceName, theResourceId), theForcedIdDao).get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<Long> translateForcedIdToPids(DaoConfig theDaoConfig, IIdType theId, IForcedIdDao theForcedIdDao) {
|
|
||||||
Validate.isTrue(theId.hasIdPart());
|
|
||||||
|
|
||||||
if (theDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY && isValidPid(theId)) {
|
|
||||||
return Collections.singletonList(theId.getIdPartAsLong());
|
|
||||||
} else {
|
|
||||||
List<ForcedId> forcedId;
|
|
||||||
if (theId.hasResourceType()) {
|
|
||||||
forcedId = theForcedIdDao.findByTypeAndForcedId(theId.getResourceType(), theId.getIdPart());
|
|
||||||
} else {
|
|
||||||
forcedId = theForcedIdDao.findByForcedId(theId.getIdPart());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forcedId.isEmpty() == false) {
|
|
||||||
List<Long> retVal = new ArrayList<>(forcedId.size());
|
|
||||||
for (ForcedId next : forcedId) {
|
|
||||||
retVal.add(next.getResourcePid());
|
|
||||||
}
|
|
||||||
return retVal;
|
|
||||||
} else {
|
|
||||||
throw new ResourceNotFoundException(theId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SearchParameterMap translateMatchUrl(IDao theCallingDao, FhirContext theContext, String
|
|
||||||
theMatchUrl, RuntimeResourceDefinition resourceDef) {
|
|
||||||
SearchParameterMap paramMap = new SearchParameterMap();
|
|
||||||
List<NameValuePair> parameters = translateMatchUrl(theMatchUrl);
|
|
||||||
|
|
||||||
ArrayListMultimap<String, QualifiedParamList> nameToParamLists = ArrayListMultimap.create();
|
|
||||||
for (NameValuePair next : parameters) {
|
|
||||||
if (isBlank(next.getValue())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String paramName = next.getName();
|
|
||||||
String qualifier = null;
|
|
||||||
for (int i = 0; i < paramName.length(); i++) {
|
|
||||||
switch (paramName.charAt(i)) {
|
|
||||||
case '.':
|
|
||||||
case ':':
|
|
||||||
qualifier = paramName.substring(i);
|
|
||||||
paramName = paramName.substring(0, i);
|
|
||||||
i = Integer.MAX_VALUE - 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QualifiedParamList paramList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifier, next.getValue());
|
|
||||||
nameToParamLists.put(paramName, paramList);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String nextParamName : nameToParamLists.keySet()) {
|
|
||||||
List<QualifiedParamList> paramList = nameToParamLists.get(nextParamName);
|
|
||||||
if (Constants.PARAM_LASTUPDATED.equals(nextParamName)) {
|
|
||||||
if (paramList != null && paramList.size() > 0) {
|
|
||||||
if (paramList.size() > 2) {
|
|
||||||
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Can not have more than 2 " + Constants.PARAM_LASTUPDATED + " parameter repetitions");
|
|
||||||
} else {
|
|
||||||
DateRangeParam p1 = new DateRangeParam();
|
|
||||||
p1.setValuesAsQueryTokens(theContext, nextParamName, paramList);
|
|
||||||
paramMap.setLastUpdated(p1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Constants.PARAM_HAS.equals(nextParamName)) {
|
|
||||||
IQueryParameterAnd<?> param = ParameterUtil.parseQueryParams(theContext, RestSearchParameterTypeEnum.HAS, nextParamName, paramList);
|
|
||||||
paramMap.add(nextParamName, param);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Constants.PARAM_COUNT.equals(nextParamName)) {
|
|
||||||
if (paramList.size() > 0 && paramList.get(0).size() > 0) {
|
|
||||||
String intString = paramList.get(0).get(0);
|
|
||||||
try {
|
|
||||||
paramMap.setCount(Integer.parseInt(intString));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new InvalidRequestException("Invalid " + Constants.PARAM_COUNT + " value: " + intString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (RESOURCE_META_PARAMS.containsKey(nextParamName)) {
|
|
||||||
if (isNotBlank(paramList.get(0).getQualifier()) && paramList.get(0).getQualifier().startsWith(".")) {
|
|
||||||
throw new InvalidRequestException("Invalid parameter chain: " + nextParamName + paramList.get(0).getQualifier());
|
|
||||||
}
|
|
||||||
IQueryParameterAnd<?> type = newInstanceAnd(nextParamName);
|
|
||||||
type.setValuesAsQueryTokens(theContext, nextParamName, (paramList));
|
|
||||||
paramMap.add(nextParamName, type);
|
|
||||||
} else if (nextParamName.startsWith("_")) {
|
|
||||||
// ignore these since they aren't search params (e.g. _sort)
|
|
||||||
} else {
|
|
||||||
RuntimeSearchParam paramDef = theCallingDao.getSearchParamByName(resourceDef, nextParamName);
|
|
||||||
if (paramDef == null) {
|
|
||||||
throw new InvalidRequestException(
|
|
||||||
"Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName);
|
|
||||||
}
|
|
||||||
|
|
||||||
IQueryParameterAnd<?> param = ParameterUtil.parseQueryParams(theContext, paramDef, nextParamName, paramList);
|
|
||||||
paramMap.add(nextParamName, param);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return paramMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<NameValuePair> translateMatchUrl(String theMatchUrl) {
|
|
||||||
List<NameValuePair> parameters;
|
|
||||||
String matchUrl = theMatchUrl;
|
|
||||||
int questionMarkIndex = matchUrl.indexOf('?');
|
|
||||||
if (questionMarkIndex != -1) {
|
|
||||||
matchUrl = matchUrl.substring(questionMarkIndex + 1);
|
|
||||||
}
|
|
||||||
matchUrl = matchUrl.replace("|", "%7C");
|
|
||||||
matchUrl = matchUrl.replace("=>=", "=%3E%3D");
|
|
||||||
matchUrl = matchUrl.replace("=<=", "=%3C%3D");
|
|
||||||
matchUrl = matchUrl.replace("=>", "=%3E");
|
|
||||||
matchUrl = matchUrl.replace("=<", "=%3C");
|
|
||||||
if (matchUrl.contains(" ")) {
|
|
||||||
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)");
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&');
|
|
||||||
return parameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void validateResourceType(BaseHasResource theEntity, String theResourceName) {
|
public static void validateResourceType(BaseHasResource theEntity, String theResourceName) {
|
||||||
if (!theResourceName.equals(theEntity.getResourceType())) {
|
if (!theResourceName.equals(theEntity.getResourceType())) {
|
||||||
throw new ResourceNotFoundException(
|
throw new ResourceNotFoundException(
|
||||||
|
@ -2145,28 +1762,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ISearchParamExtractor getSearchParamExtractor() {
|
|
||||||
return mySearchParamExtractor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ISearchParamRegistry getSearchParamRegistry() {
|
public ISearchParamRegistry getSearchParamRegistry() {
|
||||||
return mySearchParamRegistry;
|
return mySearchParamRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public EntityManager getEntityManager() {
|
|
||||||
return myEntityManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> getResourceTypeToDao() {
|
|
||||||
return myResourceTypeToDao;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IForcedIdDao getForcedIdDao() {
|
|
||||||
return myForcedIdDao;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
|
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
|
||||||
import ca.uhn.fhir.jpa.entity.*;
|
import ca.uhn.fhir.jpa.entity.*;
|
||||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
||||||
|
@ -75,8 +74,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
protected PlatformTransactionManager myPlatformTransactionManager;
|
protected PlatformTransactionManager myPlatformTransactionManager;
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
protected IFulltextSearchSvc mySearchDao;
|
protected IFulltextSearchSvc mySearchDao;
|
||||||
@Autowired()
|
|
||||||
protected ISearchResultDao mySearchResultDao;
|
|
||||||
@Autowired
|
@Autowired
|
||||||
protected DaoConfig myDaoConfig;
|
protected DaoConfig myDaoConfig;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -86,6 +83,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
private String mySecondaryPrimaryKeyParamName;
|
private String mySecondaryPrimaryKeyParamName;
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISearchParamRegistry mySearchParamRegistry;
|
private ISearchParamRegistry mySearchParamRegistry;
|
||||||
|
@Autowired
|
||||||
|
private MatchUrlService myMatchUrlService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
|
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
|
||||||
|
@ -272,7 +271,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
public DeleteMethodOutcome deleteByUrl(String theUrl, List<DeleteConflict> deleteConflicts, RequestDetails theRequest) {
|
public DeleteMethodOutcome deleteByUrl(String theUrl, List<DeleteConflict> deleteConflicts, RequestDetails theRequest) {
|
||||||
StopWatch w = new StopWatch();
|
StopWatch w = new StopWatch();
|
||||||
|
|
||||||
Set<Long> resource = processMatchUrl(theUrl, myResourceType);
|
Set<Long> resource = myMatchUrlService.processMatchUrl(theUrl, myResourceType);
|
||||||
if (resource.size() > 1) {
|
if (resource.size() > 1) {
|
||||||
if (myDaoConfig.isAllowMultipleDelete() == false) {
|
if (myDaoConfig.isAllowMultipleDelete() == false) {
|
||||||
throw new PreconditionFailedException(getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "DELETE", theUrl, resource.size()));
|
throw new PreconditionFailedException(getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "DELETE", theUrl, resource.size()));
|
||||||
|
@ -372,7 +371,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
entity.setResourceType(toResourceName(theResource));
|
entity.setResourceType(toResourceName(theResource));
|
||||||
|
|
||||||
if (isNotBlank(theIfNoneExist)) {
|
if (isNotBlank(theIfNoneExist)) {
|
||||||
Set<Long> match = processMatchUrl(theIfNoneExist, myResourceType);
|
Set<Long> match = myMatchUrlService.processMatchUrl(theIfNoneExist, myResourceType);
|
||||||
if (match.size() > 1) {
|
if (match.size() > 1) {
|
||||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size());
|
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size());
|
||||||
throw new PreconditionFailedException(msg);
|
throw new PreconditionFailedException(msg);
|
||||||
|
@ -793,7 +792,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
myResourceName = def.getName();
|
myResourceName = def.getName();
|
||||||
|
|
||||||
if (mySecondaryPrimaryKeyParamName != null) {
|
if (mySecondaryPrimaryKeyParamName != null) {
|
||||||
RuntimeSearchParam sp = getSearchParamByName(def, mySecondaryPrimaryKeyParamName);
|
RuntimeSearchParam sp = mySearchParamRegistry.getSearchParamByName(def, mySecondaryPrimaryKeyParamName);
|
||||||
if (sp == null) {
|
if (sp == null) {
|
||||||
throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]");
|
throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]");
|
||||||
}
|
}
|
||||||
|
@ -849,7 +848,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Long> processMatchUrl(String theMatchUrl) {
|
public Set<Long> processMatchUrl(String theMatchUrl) {
|
||||||
return processMatchUrl(theMatchUrl, getResourceType());
|
return myMatchUrlService.processMatchUrl(theMatchUrl, getResourceType());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -911,7 +910,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId) {
|
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId) {
|
||||||
validateResourceTypeAndThrowIllegalArgumentException(theId);
|
validateResourceTypeAndThrowIllegalArgumentException(theId);
|
||||||
|
|
||||||
Long pid = translateForcedIdToPid(getResourceName(), theId.getIdPart());
|
Long pid = myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart());
|
||||||
BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid);
|
BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid);
|
||||||
|
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
|
@ -951,7 +950,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ResourceTable readEntityLatestVersion(IIdType theId) {
|
protected ResourceTable readEntityLatestVersion(IIdType theId) {
|
||||||
ResourceTable entity = myEntityManager.find(ResourceTable.class, translateForcedIdToPid(getResourceName(), theId.getIdPart()));
|
ResourceTable entity = myEntityManager.find(ResourceTable.class, myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart()));
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
throw new ResourceNotFoundException(theId);
|
throw new ResourceNotFoundException(theId);
|
||||||
}
|
}
|
||||||
|
@ -1192,7 +1191,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
|
|
||||||
// Should not be null since the check above would have caught it
|
// Should not be null since the check above would have caught it
|
||||||
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceName);
|
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceName);
|
||||||
RuntimeSearchParam paramDef = getSearchParamByName(resourceDef, qualifiedParamName.getParamName());
|
RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(resourceDef, qualifiedParamName.getParamName());
|
||||||
|
|
||||||
for (String nextValue : theSource.get(nextParamName)) {
|
for (String nextValue : theSource.get(nextParamName)) {
|
||||||
if (isNotBlank(nextValue)) {
|
if (isNotBlank(nextValue)) {
|
||||||
|
@ -1232,7 +1231,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
IIdType resourceId;
|
IIdType resourceId;
|
||||||
if (isNotBlank(theMatchUrl)) {
|
if (isNotBlank(theMatchUrl)) {
|
||||||
StopWatch sw = new StopWatch();
|
StopWatch sw = new StopWatch();
|
||||||
Set<Long> match = processMatchUrl(theMatchUrl, myResourceType);
|
Set<Long> match = myMatchUrlService.processMatchUrl(theMatchUrl, myResourceType);
|
||||||
if (match.size() > 1) {
|
if (match.size() > 1) {
|
||||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "UPDATE", theMatchUrl, match.size());
|
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "UPDATE", theMatchUrl, match.size());
|
||||||
throw new PreconditionFailedException(msg);
|
throw new PreconditionFailedException(msg);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package ca.uhn.fhir.jpa.dao;
|
package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
|
||||||
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
|
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
|
||||||
import ca.uhn.fhir.jpa.util.ExpungeOptions;
|
import ca.uhn.fhir.jpa.util.ExpungeOptions;
|
||||||
import ca.uhn.fhir.jpa.util.ExpungeOutcome;
|
import ca.uhn.fhir.jpa.util.ExpungeOutcome;
|
||||||
|
@ -51,8 +50,6 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("myResourceCountsCache")
|
@Qualifier("myResourceCountsCache")
|
||||||
public ResourceCountCache myResourceCountsCache;
|
public ResourceCountCache myResourceCountsCache;
|
||||||
@Autowired
|
|
||||||
private IForcedIdDao myForcedIdDao;
|
|
||||||
private ReentrantLock myReindexLock = new ReentrantLock(false);
|
private ReentrantLock myReindexLock = new ReentrantLock(false);
|
||||||
@Autowired
|
@Autowired
|
||||||
private ITermConceptDao myTermConceptDao;
|
private ITermConceptDao myTermConceptDao;
|
||||||
|
@ -60,9 +57,6 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
|
||||||
private ISearchParamRegistry mySearchParamRegistry;
|
private ISearchParamRegistry mySearchParamRegistry;
|
||||||
@Autowired
|
@Autowired
|
||||||
private PlatformTransactionManager myTxManager;
|
private PlatformTransactionManager myTxManager;
|
||||||
@Autowired
|
|
||||||
private IResourceTableDao myResourceTableDao;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(propagation = Propagation.NEVER)
|
@Transactional(propagation = Propagation.NEVER)
|
||||||
|
|
|
@ -348,5 +348,16 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
|
||||||
|
|
||||||
protected abstract RuntimeSearchParam toRuntimeSp(SP theNextSp);
|
protected abstract RuntimeSearchParam toRuntimeSp(SP theNextSp);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) {
|
||||||
|
Map<String, RuntimeSearchParam> params = getActiveSearchParams(theResourceDef.getName());
|
||||||
|
return params.get(theParamName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
|
||||||
|
return getActiveSearchParams(theResourceDef.getName()).values();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,6 +156,7 @@ public class DaoConfig {
|
||||||
private List<Integer> mySearchPreFetchThresholds = Arrays.asList(500, 2000, -1);
|
private List<Integer> mySearchPreFetchThresholds = Arrays.asList(500, 2000, -1);
|
||||||
private List<WarmCacheEntry> myWarmCacheEntries = new ArrayList<>();
|
private List<WarmCacheEntry> myWarmCacheEntries = new ArrayList<>();
|
||||||
private boolean myDisableHashBasedSearches;
|
private boolean myDisableHashBasedSearches;
|
||||||
|
private boolean myEnableInMemorySubscriptionMatching = true;
|
||||||
private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC;
|
private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1448,6 +1449,50 @@ public class DaoConfig {
|
||||||
myDisableHashBasedSearches = theDisableHashBasedSearches;
|
myDisableHashBasedSearches = theDisableHashBasedSearches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to <code>false</code> (default is true) the server will not use
|
||||||
|
* in-memory subscription searching and instead use the database matcher for all subscription
|
||||||
|
* criteria matching.
|
||||||
|
* <p>
|
||||||
|
* When there are subscriptions registered
|
||||||
|
* on the server, the default behaviour is to compare the changed resource to the
|
||||||
|
* subscription criteria directly in-memory without going out to the database.
|
||||||
|
* Certain types of subscription criteria, e.g. chained references of queries with
|
||||||
|
* qualifiers or prefixes, are not supported by the in-memory matcher and will fall back
|
||||||
|
* to a database matcher.
|
||||||
|
* <p>
|
||||||
|
* The database matcher performs a query against the
|
||||||
|
* database by prepending ?id=XYZ to the subscription criteria where XYZ is the id of the changed entity
|
||||||
|
*
|
||||||
|
* @since 3.6.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
public boolean isEnableInMemorySubscriptionMatching() {
|
||||||
|
return myEnableInMemorySubscriptionMatching;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to <code>false</code> (default is true) the server will not use
|
||||||
|
* in-memory subscription searching and instead use the database matcher for all subscription
|
||||||
|
* criteria matching.
|
||||||
|
* <p>
|
||||||
|
* When there are subscriptions registered
|
||||||
|
* on the server, the default behaviour is to compare the changed resource to the
|
||||||
|
* subscription criteria directly in-memory without going out to the database.
|
||||||
|
* Certain types of subscription criteria, e.g. chained references of queries with
|
||||||
|
* qualifiers or prefixes, are not supported by the in-memory matcher and will fall back
|
||||||
|
* to a database matcher.
|
||||||
|
* <p>
|
||||||
|
* The database matcher performs a query against the
|
||||||
|
* database by prepending ?id=XYZ to the subscription criteria where XYZ is the id of the changed entity
|
||||||
|
*
|
||||||
|
* @since 3.6.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void setEnableInMemorySubscriptionMatching(boolean theEnableInMemorySubscriptionMatching) {
|
||||||
|
myEnableInMemorySubscriptionMatching = theEnableInMemorySubscriptionMatching;
|
||||||
|
}
|
||||||
|
|
||||||
public enum IndexEnabledEnum {
|
public enum IndexEnabledEnum {
|
||||||
ENABLED,
|
ENABLED,
|
||||||
DISABLED
|
DISABLED
|
||||||
|
|
|
@ -23,22 +23,27 @@ package ca.uhn.fhir.jpa.dao;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component("myDaoRegistry")
|
||||||
public class DaoRegistry implements ApplicationContextAware {
|
public class DaoRegistry implements ApplicationContextAware {
|
||||||
private ApplicationContext myAppCtx;
|
private ApplicationContext myAppCtx;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private FhirContext myCtx;
|
private FhirContext myContext;
|
||||||
|
|
||||||
private volatile Map<String, IFhirResourceDao<?>> myResourceNameToResourceDao;
|
private volatile Map<String, IFhirResourceDao<?>> myResourceNameToResourceDao;
|
||||||
private volatile IFhirSystemDao<?, ?> mySystemDao;
|
private volatile IFhirSystemDao<?, ?> mySystemDao;
|
||||||
|
|
||||||
|
@ -47,8 +52,8 @@ public class DaoRegistry implements ApplicationContextAware {
|
||||||
myAppCtx = theApplicationContext;
|
myAppCtx = theApplicationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFhirSystemDao<?, ?> getSystemDao() {
|
public IFhirSystemDao getSystemDao() {
|
||||||
IFhirSystemDao<?, ?> retVal = mySystemDao;
|
IFhirSystemDao retVal = mySystemDao;
|
||||||
if (retVal == null) {
|
if (retVal == null) {
|
||||||
retVal = myAppCtx.getBean(IFhirSystemDao.class);
|
retVal = myAppCtx.getBean(IFhirSystemDao.class);
|
||||||
mySystemDao = retVal;
|
mySystemDao = retVal;
|
||||||
|
@ -56,10 +61,11 @@ public class DaoRegistry implements ApplicationContextAware {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFhirResourceDao<?> getResourceDao(String theResourceName) {
|
public IFhirResourceDao getResourceDao(String theResourceName) {
|
||||||
IFhirResourceDao<?> retVal = getResourceNameToResourceDao().get(theResourceName);
|
init();
|
||||||
|
IFhirResourceDao retVal = myResourceNameToResourceDao.get(theResourceName);
|
||||||
if (retVal == null) {
|
if (retVal == null) {
|
||||||
List<String> supportedResourceTypes = getResourceNameToResourceDao()
|
List<String> supportedResourceTypes = myResourceNameToResourceDao
|
||||||
.keySet()
|
.keySet()
|
||||||
.stream()
|
.stream()
|
||||||
.sorted()
|
.sorted()
|
||||||
|
@ -67,26 +73,54 @@ public class DaoRegistry implements ApplicationContextAware {
|
||||||
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + theResourceName + " - Can handle: " + supportedResourceTypes);
|
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + theResourceName + " - Can handle: " + supportedResourceTypes);
|
||||||
}
|
}
|
||||||
return retVal;
|
return retVal;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends IBaseResource> IFhirResourceDao<T> getResourceDao(Class<T> theResourceType) {
|
public <R extends IBaseResource> IFhirResourceDao<R> getResourceDao(Class<R> theResourceType) {
|
||||||
String resourceName = myCtx.getResourceDefinition(theResourceType).getName();
|
IFhirResourceDao<R> retVal = getResourceDaoIfExists(theResourceType);
|
||||||
|
Validate.notNull(retVal, "No DAO exists for resource type %s - Have: %s", theResourceType, myResourceNameToResourceDao);
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends IBaseResource> IFhirResourceDao<T> getResourceDaoIfExists(Class<T> theResourceType) {
|
||||||
|
String resourceName = myContext.getResourceDefinition(theResourceType).getName();
|
||||||
return (IFhirResourceDao<T>) getResourceDao(resourceName);
|
return (IFhirResourceDao<T>) getResourceDao(resourceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, IFhirResourceDao<?>> getResourceNameToResourceDao() {
|
private void init() {
|
||||||
Map<String, IFhirResourceDao<?>> retVal = myResourceNameToResourceDao;
|
if (myResourceNameToResourceDao != null && !myResourceNameToResourceDao.isEmpty()) {
|
||||||
if (retVal == null || retVal.isEmpty()) {
|
return;
|
||||||
retVal = new HashMap<>();
|
|
||||||
Map<String, IFhirResourceDao> resourceDaos = myAppCtx.getBeansOfType(IFhirResourceDao.class);
|
|
||||||
for (IFhirResourceDao nextResourceDao : resourceDaos.values()) {
|
|
||||||
RuntimeResourceDefinition nextResourceDef = myCtx.getResourceDefinition(nextResourceDao.getResourceType());
|
|
||||||
retVal.put(nextResourceDef.getName(), nextResourceDao);
|
|
||||||
}
|
}
|
||||||
myResourceNameToResourceDao = retVal;
|
|
||||||
|
Map<String, IFhirResourceDao> resourceDaos = myAppCtx.getBeansOfType(IFhirResourceDao.class);
|
||||||
|
|
||||||
|
initializeMaps(resourceDaos.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeMaps(Collection<IFhirResourceDao> theResourceDaos) {
|
||||||
|
|
||||||
|
myResourceNameToResourceDao = new HashMap<>();
|
||||||
|
|
||||||
|
for (IFhirResourceDao nextResourceDao : theResourceDaos) {
|
||||||
|
RuntimeResourceDefinition nextResourceDef = myContext.getResourceDefinition(nextResourceDao.getResourceType());
|
||||||
|
myResourceNameToResourceDao.put(nextResourceDef.getName(), nextResourceDao);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFhirResourceDao getDaoOrThrowException(Class<? extends IBaseResource> theClass) {
|
||||||
|
IFhirResourceDao retVal = getResourceDao(theClass);
|
||||||
|
if (retVal == null) {
|
||||||
|
List<String> supportedResourceNames = myResourceNameToResourceDao
|
||||||
|
.keySet()
|
||||||
|
.stream()
|
||||||
|
.map(t -> myContext.getResourceDefinition(t).getName())
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + myContext.getResourceDefinition(theClass).getName() + " - Can handle: " + supportedResourceNames);
|
||||||
}
|
}
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setResourceDaos(Collection<IFhirResourceDao> theResourceDaos) {
|
||||||
|
initializeMaps(theResourceDaos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,6 @@ import org.springframework.transaction.TransactionStatus;
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.transaction.support.TransactionCallback;
|
import org.springframework.transaction.support.TransactionCallback;
|
||||||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
|
@ -82,6 +81,8 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
|
||||||
@Autowired
|
@Autowired
|
||||||
private PlatformTransactionManager myTxManager;
|
private PlatformTransactionManager myTxManager;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private MatchUrlService myMatchUrlService;
|
||||||
|
@Autowired
|
||||||
private DaoRegistry myDaoRegistry;
|
private DaoRegistry myDaoRegistry;
|
||||||
|
|
||||||
private Bundle batch(final RequestDetails theRequestDetails, Bundle theRequest) {
|
private Bundle batch(final RequestDetails theRequestDetails, Bundle theRequest) {
|
||||||
|
@ -243,7 +244,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
|
||||||
requestDetails.setParameters(new HashMap<String, String[]>());
|
requestDetails.setParameters(new HashMap<String, String[]>());
|
||||||
if (qIndex != -1) {
|
if (qIndex != -1) {
|
||||||
String params = url.substring(qIndex);
|
String params = url.substring(qIndex);
|
||||||
List<NameValuePair> parameters = translateMatchUrl(params);
|
List<NameValuePair> parameters = myMatchUrlService.translateMatchUrl(params);
|
||||||
for (NameValuePair next : parameters) {
|
for (NameValuePair next : parameters) {
|
||||||
paramValues.put(next.getName(), next.getValue());
|
paramValues.put(next.getName(), next.getValue());
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
|
@ -41,7 +42,6 @@ import org.hibernate.search.jpa.FullTextQuery;
|
||||||
import org.hibernate.search.query.dsl.BooleanJunction;
|
import org.hibernate.search.query.dsl.BooleanJunction;
|
||||||
import org.hibernate.search.query.dsl.QueryBuilder;
|
import org.hibernate.search.query.dsl.QueryBuilder;
|
||||||
import org.hl7.fhir.dstu3.model.BaseResource;
|
import org.hl7.fhir.dstu3.model.BaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
@ -65,11 +65,14 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
|
||||||
@Autowired
|
@Autowired
|
||||||
protected IForcedIdDao myForcedIdDao;
|
protected IForcedIdDao myForcedIdDao;
|
||||||
|
|
||||||
private Boolean ourDisabled;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private DaoConfig myDaoConfig;
|
private DaoConfig myDaoConfig;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IdHelperService myIdHelperService;
|
||||||
|
|
||||||
|
private Boolean ourDisabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
|
@ -225,7 +228,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
|
||||||
StringParam idParm = (StringParam) idParam;
|
StringParam idParm = (StringParam) idParam;
|
||||||
idParamValue = idParm.getValue();
|
idParamValue = idParm.getValue();
|
||||||
}
|
}
|
||||||
pid = BaseHapiFhirDao.translateForcedIdToPid(myDaoConfig, theResourceName, idParamValue, myForcedIdDao);
|
pid = myIdHelperService.translateForcedIdToPid(theResourceName, idParamValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
Long referencingPid = pid;
|
Long referencingPid = pid;
|
||||||
|
@ -278,7 +281,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
|
||||||
if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) {
|
if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) {
|
||||||
throw new InvalidRequestException("Invalid context: " + theContext);
|
throw new InvalidRequestException("Invalid context: " + theContext);
|
||||||
}
|
}
|
||||||
Long pid = BaseHapiFhirDao.translateForcedIdToPid( myDaoConfig, contextParts[0], contextParts[1], myForcedIdDao);
|
Long pid = myIdHelperService.translateForcedIdToPid(contextParts[0], contextParts[1]);
|
||||||
|
|
||||||
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);
|
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
package ca.uhn.fhir.jpa.dao;
|
package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.jpa.entity.BaseHasResource;
|
import ca.uhn.fhir.jpa.entity.BaseHasResource;
|
||||||
import ca.uhn.fhir.jpa.entity.IBaseResourceEntity;
|
import ca.uhn.fhir.jpa.entity.IBaseResourceEntity;
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTag;
|
import ca.uhn.fhir.jpa.entity.ResourceTag;
|
||||||
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
|
@ -41,10 +37,6 @@ public interface IDao {
|
||||||
|
|
||||||
FhirContext getContext();
|
FhirContext getContext();
|
||||||
|
|
||||||
RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName);
|
|
||||||
|
|
||||||
Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populate all of the runtime dependencies that a bundle provider requires in order to work
|
* Populate all of the runtime dependencies that a bundle provider requires in order to work
|
||||||
*/
|
*/
|
||||||
|
@ -52,12 +44,9 @@ public interface IDao {
|
||||||
|
|
||||||
ISearchBuilder newSearchBuilder();
|
ISearchBuilder newSearchBuilder();
|
||||||
|
|
||||||
void populateFullTextFields(IBaseResource theResource, ResourceTable theEntity);
|
|
||||||
|
|
||||||
<R extends IBaseResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType);
|
|
||||||
|
|
||||||
IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation);
|
IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation);
|
||||||
|
|
||||||
<R extends IBaseResource> R toResource(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<ResourceTag> theTagList, boolean theForHistoryOperation);
|
<R extends IBaseResource> R toResource(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<ResourceTag> theTagList, boolean theForHistoryOperation);
|
||||||
|
|
||||||
|
ISearchParamRegistry getSearchParamRegistry();
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ public interface ISearchBuilder {
|
||||||
void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation, EntityManager theEntityManager,
|
void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation, EntityManager theEntityManager,
|
||||||
FhirContext theContext, IDao theDao);
|
FhirContext theContext, IDao theDao);
|
||||||
|
|
||||||
Set<Long> loadIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode,
|
Set<Long> loadIncludes(FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode,
|
||||||
DateRangeParam theLastUpdated, String theSearchIdOrDescription);
|
DateRangeParam theLastUpdated, String theSearchIdOrDescription);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,9 +20,11 @@ package ca.uhn.fhir.jpa.dao;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
|
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -53,4 +55,8 @@ public interface ISearchParamRegistry {
|
||||||
* Request that the cache be refreshed at the next convenient time (in a different thread)
|
* Request that the cache be refreshed at the next convenient time (in a different thread)
|
||||||
*/
|
*/
|
||||||
void requestRefresh();
|
void requestRefresh();
|
||||||
|
|
||||||
|
RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName);
|
||||||
|
|
||||||
|
Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.trim;
|
||||||
|
|
||||||
|
public class LogicalReferenceHelper {
|
||||||
|
|
||||||
|
public static boolean isLogicalReference(DaoConfig myConfig, IIdType theId) {
|
||||||
|
Set<String> treatReferencesAsLogical = myConfig.getTreatReferencesAsLogical();
|
||||||
|
if (treatReferencesAsLogical != null) {
|
||||||
|
for (String nextLogicalRef : treatReferencesAsLogical) {
|
||||||
|
nextLogicalRef = trim(nextLogicalRef);
|
||||||
|
if (nextLogicalRef.charAt(nextLogicalRef.length() - 1) == '*') {
|
||||||
|
if (theId.getValue().startsWith(nextLogicalRef.substring(0, nextLogicalRef.length() - 1))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (theId.getValue().equals(nextLogicalRef)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,186 @@
|
||||||
|
package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
|
import ca.uhn.fhir.model.api.IQueryParameterAnd;
|
||||||
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
||||||
|
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||||
|
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.util.CoverageIgnore;
|
||||||
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.utils.URLEncodedUtils;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class MatchUrlService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FhirContext myContext;
|
||||||
|
@Autowired
|
||||||
|
private DaoRegistry myDaoRegistry;
|
||||||
|
@Autowired
|
||||||
|
private MatchUrlService myMatchUrlService;
|
||||||
|
@Autowired
|
||||||
|
private ISearchParamRegistry mySearchParamRegistry;
|
||||||
|
|
||||||
|
public <R extends IBaseResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType) {
|
||||||
|
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceType);
|
||||||
|
|
||||||
|
SearchParameterMap paramMap = myMatchUrlService.translateMatchUrl(theMatchUrl, resourceDef);
|
||||||
|
paramMap.setLoadSynchronous(true);
|
||||||
|
|
||||||
|
if (paramMap.isEmpty() && paramMap.getLastUpdated() == null) {
|
||||||
|
throw new InvalidRequestException("Invalid match URL[" + theMatchUrl + "] - URL has no search parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
IFhirResourceDao<R> dao = myDaoRegistry.getResourceDao(theResourceType);
|
||||||
|
if (dao == null) {
|
||||||
|
throw new InternalErrorException("No DAO for resource type: " + theResourceType.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return dao.searchForIds(paramMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchParameterMap translateMatchUrl(String
|
||||||
|
theMatchUrl, RuntimeResourceDefinition resourceDef) {
|
||||||
|
SearchParameterMap paramMap = new SearchParameterMap();
|
||||||
|
List<NameValuePair> parameters = translateMatchUrl(theMatchUrl);
|
||||||
|
|
||||||
|
ArrayListMultimap<String, QualifiedParamList> nameToParamLists = ArrayListMultimap.create();
|
||||||
|
for (NameValuePair next : parameters) {
|
||||||
|
if (isBlank(next.getValue())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String paramName = next.getName();
|
||||||
|
String qualifier = null;
|
||||||
|
for (int i = 0; i < paramName.length(); i++) {
|
||||||
|
switch (paramName.charAt(i)) {
|
||||||
|
case '.':
|
||||||
|
case ':':
|
||||||
|
qualifier = paramName.substring(i);
|
||||||
|
paramName = paramName.substring(0, i);
|
||||||
|
i = Integer.MAX_VALUE - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QualifiedParamList paramList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifier, next.getValue());
|
||||||
|
nameToParamLists.put(paramName, paramList);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String nextParamName : nameToParamLists.keySet()) {
|
||||||
|
List<QualifiedParamList> paramList = nameToParamLists.get(nextParamName);
|
||||||
|
if (Constants.PARAM_LASTUPDATED.equals(nextParamName)) {
|
||||||
|
if (paramList != null && paramList.size() > 0) {
|
||||||
|
if (paramList.size() > 2) {
|
||||||
|
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Can not have more than 2 " + Constants.PARAM_LASTUPDATED + " parameter repetitions");
|
||||||
|
} else {
|
||||||
|
DateRangeParam p1 = new DateRangeParam();
|
||||||
|
p1.setValuesAsQueryTokens(myContext, nextParamName, paramList);
|
||||||
|
paramMap.setLastUpdated(p1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Constants.PARAM_HAS.equals(nextParamName)) {
|
||||||
|
IQueryParameterAnd<?> param = ParameterUtil.parseQueryParams(myContext, RestSearchParameterTypeEnum.HAS, nextParamName, paramList);
|
||||||
|
paramMap.add(nextParamName, param);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Constants.PARAM_COUNT.equals(nextParamName)) {
|
||||||
|
if (paramList.size() > 0 && paramList.get(0).size() > 0) {
|
||||||
|
String intString = paramList.get(0).get(0);
|
||||||
|
try {
|
||||||
|
paramMap.setCount(Integer.parseInt(intString));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new InvalidRequestException("Invalid " + Constants.PARAM_COUNT + " value: " + intString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ResourceMetaParams.RESOURCE_META_PARAMS.containsKey(nextParamName)) {
|
||||||
|
if (isNotBlank(paramList.get(0).getQualifier()) && paramList.get(0).getQualifier().startsWith(".")) {
|
||||||
|
throw new InvalidRequestException("Invalid parameter chain: " + nextParamName + paramList.get(0).getQualifier());
|
||||||
|
}
|
||||||
|
IQueryParameterAnd<?> type = newInstanceAnd(nextParamName);
|
||||||
|
type.setValuesAsQueryTokens(myContext, nextParamName, (paramList));
|
||||||
|
paramMap.add(nextParamName, type);
|
||||||
|
} else if (nextParamName.startsWith("_")) {
|
||||||
|
// ignore these since they aren't search params (e.g. _sort)
|
||||||
|
} else {
|
||||||
|
RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(resourceDef, nextParamName);
|
||||||
|
if (paramDef == null) {
|
||||||
|
throw new InvalidRequestException(
|
||||||
|
"Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName);
|
||||||
|
}
|
||||||
|
|
||||||
|
IQueryParameterAnd<?> param = ParameterUtil.parseQueryParams(myContext, paramDef, nextParamName, paramList);
|
||||||
|
paramMap.add(nextParamName, param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paramMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<NameValuePair> translateMatchUrl(String theMatchUrl) {
|
||||||
|
List<NameValuePair> parameters;
|
||||||
|
String matchUrl = theMatchUrl;
|
||||||
|
int questionMarkIndex = matchUrl.indexOf('?');
|
||||||
|
if (questionMarkIndex != -1) {
|
||||||
|
matchUrl = matchUrl.substring(questionMarkIndex + 1);
|
||||||
|
}
|
||||||
|
matchUrl = matchUrl.replace("|", "%7C");
|
||||||
|
matchUrl = matchUrl.replace("=>=", "=%3E%3D");
|
||||||
|
matchUrl = matchUrl.replace("=<=", "=%3C%3D");
|
||||||
|
matchUrl = matchUrl.replace("=>", "=%3E");
|
||||||
|
matchUrl = matchUrl.replace("=<", "=%3C");
|
||||||
|
if (matchUrl.contains(" ")) {
|
||||||
|
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)");
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&');
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@CoverageIgnore
|
||||||
|
protected IQueryParameterAnd newInstanceAnd(String chain) {
|
||||||
|
IQueryParameterAnd type;
|
||||||
|
Class<? extends IQueryParameterAnd> clazz = ResourceMetaParams.RESOURCE_META_AND_PARAMS.get(chain);
|
||||||
|
try {
|
||||||
|
type = clazz.newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InternalErrorException("Failure creating instance of " + clazz, e);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@CoverageIgnore
|
||||||
|
public IQueryParameterType newInstanceType(String chain) {
|
||||||
|
IQueryParameterType type;
|
||||||
|
Class<? extends IQueryParameterType> clazz = ResourceMetaParams.RESOURCE_META_PARAMS.get(chain);
|
||||||
|
try {
|
||||||
|
type = clazz.newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InternalErrorException("Failure creating instance of " + clazz, e);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.model.api.IQueryParameterAnd;
|
||||||
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.param.*;
|
||||||
|
import org.hl7.fhir.r4.model.BaseResource;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ResourceMetaParams {
|
||||||
|
/**
|
||||||
|
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
|
||||||
|
*/
|
||||||
|
public static final Map<String, Class<? extends IQueryParameterAnd<?>>> RESOURCE_META_AND_PARAMS;
|
||||||
|
/**
|
||||||
|
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
|
||||||
|
*/
|
||||||
|
public static final Map<String, Class<? extends IQueryParameterType>> RESOURCE_META_PARAMS;
|
||||||
|
public static final Set<String> EXCLUDE_ELEMENTS_IN_ENCODED;
|
||||||
|
|
||||||
|
static {
|
||||||
|
Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>();
|
||||||
|
Map<String, Class<? extends IQueryParameterAnd<?>>> resourceMetaAndParams = new HashMap<String, Class<? extends IQueryParameterAnd<?>>>();
|
||||||
|
resourceMetaParams.put(BaseResource.SP_RES_ID, StringParam.class);
|
||||||
|
resourceMetaAndParams.put(BaseResource.SP_RES_ID, StringAndListParam.class);
|
||||||
|
resourceMetaParams.put(BaseResource.SP_RES_LANGUAGE, StringParam.class);
|
||||||
|
resourceMetaAndParams.put(BaseResource.SP_RES_LANGUAGE, StringAndListParam.class);
|
||||||
|
resourceMetaParams.put(Constants.PARAM_TAG, TokenParam.class);
|
||||||
|
resourceMetaAndParams.put(Constants.PARAM_TAG, TokenAndListParam.class);
|
||||||
|
resourceMetaParams.put(Constants.PARAM_PROFILE, UriParam.class);
|
||||||
|
resourceMetaAndParams.put(Constants.PARAM_PROFILE, UriAndListParam.class);
|
||||||
|
resourceMetaParams.put(Constants.PARAM_SECURITY, TokenParam.class);
|
||||||
|
resourceMetaAndParams.put(Constants.PARAM_SECURITY, TokenAndListParam.class);
|
||||||
|
RESOURCE_META_PARAMS = Collections.unmodifiableMap(resourceMetaParams);
|
||||||
|
RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams);
|
||||||
|
|
||||||
|
HashSet<String> excludeElementsInEncoded = new HashSet<String>();
|
||||||
|
excludeElementsInEncoded.add("id");
|
||||||
|
excludeElementsInEncoded.add("*.meta");
|
||||||
|
EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded);
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,10 +21,11 @@ package ca.uhn.fhir.jpa.dao;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.context.*;
|
import ca.uhn.fhir.context.*;
|
||||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
|
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.IResourceSearchViewDao;
|
import ca.uhn.fhir.jpa.dao.data.IResourceSearchViewDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
|
import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
import ca.uhn.fhir.jpa.dao.index.ResourceIndexedSearchParams;
|
import ca.uhn.fhir.jpa.dao.index.ResourceIndexedSearchParams;
|
||||||
import ca.uhn.fhir.jpa.entity.*;
|
import ca.uhn.fhir.jpa.entity.*;
|
||||||
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
|
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
|
||||||
|
@ -69,10 +70,15 @@ import org.hibernate.query.criteria.internal.predicate.BooleanStaticAssertionPre
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.PersistenceContext;
|
||||||
|
import javax.persistence.PersistenceContextType;
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
import javax.persistence.criteria.*;
|
import javax.persistence.criteria.*;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
@ -87,6 +93,8 @@ import static org.apache.commons.lang3.StringUtils.*;
|
||||||
* searches for resources
|
* searches for resources
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("JpaQlInspection")
|
@SuppressWarnings("JpaQlInspection")
|
||||||
|
@Component
|
||||||
|
@Scope("prototype")
|
||||||
public class SearchBuilder implements ISearchBuilder {
|
public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
private static final List<Long> EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<>());
|
private static final List<Long> EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<>());
|
||||||
|
@ -97,26 +105,42 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
private static String ourLastHandlerThreadForUnitTest;
|
private static String ourLastHandlerThreadForUnitTest;
|
||||||
private static boolean ourTrackHandlersForUnitTest;
|
private static boolean ourTrackHandlersForUnitTest;
|
||||||
private final boolean myDontUseHashesForSearch;
|
private final boolean myDontUseHashesForSearch;
|
||||||
|
private final DaoConfig myDaoConfig;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
protected IResourceTagDao myResourceTagDao;
|
protected IResourceTagDao myResourceTagDao;
|
||||||
|
@Autowired
|
||||||
private IResourceSearchViewDao myResourceSearchViewDao;
|
private IResourceSearchViewDao myResourceSearchViewDao;
|
||||||
|
@Autowired
|
||||||
|
private FhirContext myContext;
|
||||||
|
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||||
|
protected EntityManager myEntityManager;
|
||||||
|
@Autowired
|
||||||
|
private IdHelperService myIdHelperService;
|
||||||
|
@Autowired(required = false)
|
||||||
|
private IFulltextSearchSvc myFulltextSearchSvc;
|
||||||
|
@Autowired
|
||||||
|
private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
|
||||||
|
@Autowired
|
||||||
|
private ISearchParamRegistry mySearchParamRegistry;
|
||||||
|
@Autowired
|
||||||
|
private IHapiTerminologySvc myTerminologySvc;
|
||||||
|
@Autowired
|
||||||
|
private MatchUrlService myMatchUrlService;
|
||||||
|
@Autowired
|
||||||
|
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
|
||||||
|
|
||||||
private List<Long> myAlsoIncludePids;
|
private List<Long> myAlsoIncludePids;
|
||||||
private CriteriaBuilder myBuilder;
|
private CriteriaBuilder myBuilder;
|
||||||
private BaseHapiFhirDao<?> myCallingDao;
|
private BaseHapiFhirDao<?> myCallingDao;
|
||||||
private FhirContext myContext;
|
|
||||||
private EntityManager myEntityManager;
|
|
||||||
private IForcedIdDao myForcedIdDao;
|
|
||||||
private IFulltextSearchSvc myFulltextSearchSvc;
|
|
||||||
private Map<JoinKey, Join<?, ?>> myIndexJoins = Maps.newHashMap();
|
private Map<JoinKey, Join<?, ?>> myIndexJoins = Maps.newHashMap();
|
||||||
private SearchParameterMap myParams;
|
private SearchParameterMap myParams;
|
||||||
private ArrayList<Predicate> myPredicates;
|
private ArrayList<Predicate> myPredicates;
|
||||||
private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
|
|
||||||
private String myResourceName;
|
private String myResourceName;
|
||||||
private AbstractQuery<Long> myResourceTableQuery;
|
private AbstractQuery<Long> myResourceTableQuery;
|
||||||
private Root<ResourceTable> myResourceTableRoot;
|
private Root<ResourceTable> myResourceTableRoot;
|
||||||
private Class<? extends IBaseResource> myResourceType;
|
private Class<? extends IBaseResource> myResourceType;
|
||||||
private ISearchParamRegistry mySearchParamRegistry;
|
|
||||||
private String mySearchUuid;
|
private String mySearchUuid;
|
||||||
private IHapiTerminologySvc myTerminologySvc;
|
|
||||||
private int myFetchSize;
|
private int myFetchSize;
|
||||||
private Integer myMaxResultsToFetch;
|
private Integer myMaxResultsToFetch;
|
||||||
private Set<Long> myPidSet;
|
private Set<Long> myPidSet;
|
||||||
|
@ -124,22 +148,10 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager,
|
SearchBuilder(BaseHapiFhirDao<?> theDao) {
|
||||||
IFulltextSearchSvc theFulltextSearchSvc, BaseHapiFhirDao<?> theDao,
|
|
||||||
IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao,
|
|
||||||
IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry,
|
|
||||||
IResourceTagDao theResourceTagDao, IResourceSearchViewDao theResourceViewDao) {
|
|
||||||
myContext = theFhirContext;
|
|
||||||
myEntityManager = theEntityManager;
|
|
||||||
myFulltextSearchSvc = theFulltextSearchSvc;
|
|
||||||
myCallingDao = theDao;
|
myCallingDao = theDao;
|
||||||
myDontUseHashesForSearch = theDao.getConfig().getDisableHashBasedSearches();
|
myDaoConfig = theDao.getConfig();
|
||||||
myResourceIndexedSearchParamUriDao = theResourceIndexedSearchParamUriDao;
|
myDontUseHashesForSearch = myDaoConfig.getDisableHashBasedSearches();
|
||||||
myForcedIdDao = theForcedIdDao;
|
|
||||||
myTerminologySvc = theTerminologySvc;
|
|
||||||
mySearchParamRegistry = theSearchParamRegistry;
|
|
||||||
myResourceTagDao = theResourceTagDao;
|
|
||||||
myResourceSearchViewDao = theResourceViewDao;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -220,18 +232,18 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
assert parameterName != null;
|
assert parameterName != null;
|
||||||
String paramName = parameterName.replaceAll("\\..*", "");
|
String paramName = parameterName.replaceAll("\\..*", "");
|
||||||
RuntimeSearchParam owningParameterDef = myCallingDao.getSearchParamByName(targetResourceDefinition, paramName);
|
RuntimeSearchParam owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName);
|
||||||
if (owningParameterDef == null) {
|
if (owningParameterDef == null) {
|
||||||
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName);
|
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName);
|
||||||
}
|
}
|
||||||
|
|
||||||
owningParameterDef = myCallingDao.getSearchParamByName(targetResourceDefinition, owningParameter);
|
owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, owningParameter);
|
||||||
if (owningParameterDef == null) {
|
if (owningParameterDef == null) {
|
||||||
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + owningParameter);
|
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + owningParameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<? extends IBaseResource> resourceType = targetResourceDefinition.getImplementingClass();
|
Class<? extends IBaseResource> resourceType = targetResourceDefinition.getImplementingClass();
|
||||||
Set<Long> match = myCallingDao.processMatchUrl(matchUrl, resourceType);
|
Set<Long> match = myMatchUrlService.processMatchUrl(matchUrl, resourceType);
|
||||||
if (match.isEmpty()) {
|
if (match.isEmpty()) {
|
||||||
// Pick a PID that can never match
|
// Pick a PID that can never match
|
||||||
match = Collections.singleton(-1L);
|
match = Collections.singleton(-1L);
|
||||||
|
@ -373,7 +385,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
IIdType dt = new IdDt(ref.getBaseUrl(), ref.getResourceType(), ref.getIdPart(), null);
|
IIdType dt = new IdDt(ref.getBaseUrl(), ref.getResourceType(), ref.getIdPart(), null);
|
||||||
|
|
||||||
if (dt.hasBaseUrl()) {
|
if (dt.hasBaseUrl()) {
|
||||||
if (myCallingDao.getConfig().getTreatBaseUrlsAsLocal().contains(dt.getBaseUrl())) {
|
if (myDaoConfig.getTreatBaseUrlsAsLocal().contains(dt.getBaseUrl())) {
|
||||||
dt = dt.toUnqualified();
|
dt = dt.toUnqualified();
|
||||||
} else {
|
} else {
|
||||||
ourLog.debug("Searching for resource link with target URL: {}", dt.getValue());
|
ourLog.debug("Searching for resource link with target URL: {}", dt.getValue());
|
||||||
|
@ -385,7 +397,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
List<Long> targetPid;
|
List<Long> targetPid;
|
||||||
try {
|
try {
|
||||||
targetPid = myCallingDao.translateForcedIdToPids(dt);
|
targetPid = myIdHelperService.translateForcedIdToPids(dt);
|
||||||
} catch (ResourceNotFoundException e) {
|
} catch (ResourceNotFoundException e) {
|
||||||
// Use a PID that will never exist
|
// Use a PID that will never exist
|
||||||
targetPid = Collections.singletonList(-1L);
|
targetPid = Collections.singletonList(-1L);
|
||||||
|
@ -417,7 +429,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
if (resourceTypes.isEmpty()) {
|
if (resourceTypes.isEmpty()) {
|
||||||
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceName);
|
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceName);
|
||||||
RuntimeSearchParam searchParamByName = myCallingDao.getSearchParamByName(resourceDef, theParamName);
|
RuntimeSearchParam searchParamByName = mySearchParamRegistry.getSearchParamByName(resourceDef, theParamName);
|
||||||
if (searchParamByName == null) {
|
if (searchParamByName == null) {
|
||||||
throw new InternalErrorException("Could not find parameter " + theParamName);
|
throw new InternalErrorException("Could not find parameter " + theParamName);
|
||||||
}
|
}
|
||||||
|
@ -491,10 +503,10 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
chain = chain.substring(0, qualifierIndex);
|
chain = chain.substring(0, qualifierIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isMeta = BaseHapiFhirDao.RESOURCE_META_PARAMS.containsKey(chain);
|
boolean isMeta = ResourceMetaParams.RESOURCE_META_PARAMS.containsKey(chain);
|
||||||
RuntimeSearchParam param = null;
|
RuntimeSearchParam param = null;
|
||||||
if (!isMeta) {
|
if (!isMeta) {
|
||||||
param = myCallingDao.getSearchParamByName(typeDef, chain);
|
param = mySearchParamRegistry.getSearchParamByName(typeDef, chain);
|
||||||
if (param == null) {
|
if (param == null) {
|
||||||
ourLog.debug("Type {} doesn't have search param {}", nextType.getSimpleName(), param);
|
ourLog.debug("Type {} doesn't have search param {}", nextType.getSimpleName(), param);
|
||||||
continue;
|
continue;
|
||||||
|
@ -512,7 +524,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
chainValue.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
|
chainValue.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
|
||||||
((ReferenceParam) chainValue).setChain(remainingChain);
|
((ReferenceParam) chainValue).setChain(remainingChain);
|
||||||
} else if (isMeta) {
|
} else if (isMeta) {
|
||||||
IQueryParameterType type = BaseHapiFhirDao.newInstanceType(chain);
|
IQueryParameterType type = myMatchUrlService.newInstanceType(chain);
|
||||||
type.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
|
type.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
|
||||||
chainValue = type;
|
chainValue = type;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1162,7 +1174,6 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
private Predicate createPredicateString(IQueryParameterType theParameter, String theResourceName, String theParamName, CriteriaBuilder theBuilder,
|
private Predicate createPredicateString(IQueryParameterType theParameter, String theResourceName, String theParamName, CriteriaBuilder theBuilder,
|
||||||
From<?, ResourceIndexedSearchParamString> theFrom) {
|
From<?, ResourceIndexedSearchParamString> theFrom) {
|
||||||
String rawSearchTerm;
|
String rawSearchTerm;
|
||||||
DaoConfig daoConfig = myCallingDao.getConfig();
|
|
||||||
if (theParameter instanceof TokenParam) {
|
if (theParameter instanceof TokenParam) {
|
||||||
TokenParam id = (TokenParam) theParameter;
|
TokenParam id = (TokenParam) theParameter;
|
||||||
if (!id.isText()) {
|
if (!id.isText()) {
|
||||||
|
@ -1173,7 +1184,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
StringParam id = (StringParam) theParameter;
|
StringParam id = (StringParam) theParameter;
|
||||||
rawSearchTerm = id.getValue();
|
rawSearchTerm = id.getValue();
|
||||||
if (id.isContains()) {
|
if (id.isContains()) {
|
||||||
if (!daoConfig.isAllowContainsSearches()) {
|
if (!myDaoConfig.isAllowContainsSearches()) {
|
||||||
throw new MethodNotAllowedException(":contains modifier is disabled on this server");
|
throw new MethodNotAllowedException(":contains modifier is disabled on this server");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1191,7 +1202,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
if (myDontUseHashesForSearch) {
|
if (myDontUseHashesForSearch) {
|
||||||
String likeExpression = BaseHapiFhirDao.normalizeString(rawSearchTerm);
|
String likeExpression = BaseHapiFhirDao.normalizeString(rawSearchTerm);
|
||||||
if (myCallingDao.getConfig().isAllowContainsSearches()) {
|
if (myDaoConfig.isAllowContainsSearches()) {
|
||||||
if (theParameter instanceof StringParam) {
|
if (theParameter instanceof StringParam) {
|
||||||
if (((StringParam) theParameter).isContains()) {
|
if (((StringParam) theParameter).isContains()) {
|
||||||
likeExpression = createLeftAndRightMatchLikeExpression(likeExpression);
|
likeExpression = createLeftAndRightMatchLikeExpression(likeExpression);
|
||||||
|
@ -1230,13 +1241,13 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
String likeExpression;
|
String likeExpression;
|
||||||
if (theParameter instanceof StringParam &&
|
if (theParameter instanceof StringParam &&
|
||||||
((StringParam) theParameter).isContains() &&
|
((StringParam) theParameter).isContains() &&
|
||||||
daoConfig.isAllowContainsSearches()) {
|
myDaoConfig.isAllowContainsSearches()) {
|
||||||
likeExpression = createLeftAndRightMatchLikeExpression(normalizedString);
|
likeExpression = createLeftAndRightMatchLikeExpression(normalizedString);
|
||||||
} else {
|
} else {
|
||||||
likeExpression = createLeftMatchLikeExpression(normalizedString);
|
likeExpression = createLeftMatchLikeExpression(normalizedString);
|
||||||
}
|
}
|
||||||
|
|
||||||
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(daoConfig, theResourceName, theParamName, normalizedString);
|
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(myDaoConfig, theResourceName, theParamName, normalizedString);
|
||||||
Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash);
|
Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash);
|
||||||
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression);
|
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression);
|
||||||
return theBuilder.and(hashCode, singleCode);
|
return theBuilder.and(hashCode, singleCode);
|
||||||
|
@ -1473,7 +1484,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
* of parameters passed in
|
* of parameters passed in
|
||||||
*/
|
*/
|
||||||
ourLog.debug("Checking for unique index for query: {}", theParams.toNormalizedQueryString(myContext));
|
ourLog.debug("Checking for unique index for query: {}", theParams.toNormalizedQueryString(myContext));
|
||||||
if (myCallingDao.getConfig().isUniqueIndexesEnabled()) {
|
if (myDaoConfig.isUniqueIndexesEnabled()) {
|
||||||
if (myParams.getIncludes().isEmpty()) {
|
if (myParams.getIncludes().isEmpty()) {
|
||||||
if (myParams.getRevIncludes().isEmpty()) {
|
if (myParams.getRevIncludes().isEmpty()) {
|
||||||
if (myParams.getEverythingMode() == null) {
|
if (myParams.getEverythingMode() == null) {
|
||||||
|
@ -1600,7 +1611,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
if (myParams.get(IAnyResource.SP_RES_ID) != null) {
|
if (myParams.get(IAnyResource.SP_RES_ID) != null) {
|
||||||
StringParam idParm = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0);
|
StringParam idParm = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0);
|
||||||
Long pid = BaseHapiFhirDao.translateForcedIdToPid(myCallingDao.getConfig(), myResourceName, idParm.getValue(), myForcedIdDao);
|
Long pid = myIdHelperService.translateForcedIdToPid(myResourceName, idParm.getValue());
|
||||||
if (myAlsoIncludePids == null) {
|
if (myAlsoIncludePids == null) {
|
||||||
myAlsoIncludePids = new ArrayList<>(1);
|
myAlsoIncludePids = new ArrayList<>(1);
|
||||||
}
|
}
|
||||||
|
@ -1677,7 +1688,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate createResourceLinkPathPredicate(String theResourceName, String theParamName, From<?, ? extends ResourceLink> from) {
|
private Predicate createResourceLinkPathPredicate(String theResourceName, String theParamName, From<?, ? extends ResourceLink> from) {
|
||||||
return createResourceLinkPathPredicate(myCallingDao, myContext, theParamName, from, theResourceName);
|
return createResourceLinkPathPredicate(myContext, theParamName, from, theResourceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1713,7 +1724,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName);
|
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName);
|
||||||
RuntimeSearchParam param = myCallingDao.getSearchParamByName(resourceDef, theSort.getParamName());
|
RuntimeSearchParam param = mySearchParamRegistry.getSearchParamByName(resourceDef, theSort.getParamName());
|
||||||
if (param == null) {
|
if (param == null) {
|
||||||
throw new InvalidRequestException("Unknown sort parameter '" + theSort.getParamName() + "'");
|
throw new InvalidRequestException("Unknown sort parameter '" + theSort.getParamName() + "'");
|
||||||
}
|
}
|
||||||
|
@ -1807,7 +1818,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
String retVal = theSystem;
|
String retVal = theSystem;
|
||||||
if (retVal == null) {
|
if (retVal == null) {
|
||||||
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName);
|
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName);
|
||||||
RuntimeSearchParam param = myCallingDao.getSearchParamByName(resourceDef, theParamName);
|
RuntimeSearchParam param = mySearchParamRegistry.getSearchParamByName(resourceDef, theParamName);
|
||||||
if (param != null) {
|
if (param != null) {
|
||||||
Set<String> valueSetUris = Sets.newHashSet();
|
Set<String> valueSetUris = Sets.newHashSet();
|
||||||
for (String nextPath : param.getPathsSplit()) {
|
for (String nextPath : param.getPathsSplit()) {
|
||||||
|
@ -1957,7 +1968,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
* so it can't be Collections.emptySet() or some such thing
|
* so it can't be Collections.emptySet() or some such thing
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public HashSet<Long> loadIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes,
|
public HashSet<Long> loadIncludes(FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes,
|
||||||
boolean theReverseMode, DateRangeParam theLastUpdated, String theSearchIdOrDescription) {
|
boolean theReverseMode, DateRangeParam theLastUpdated, String theSearchIdOrDescription) {
|
||||||
if (theMatches.size() == 0) {
|
if (theMatches.size() == 0) {
|
||||||
return new HashSet<>();
|
return new HashSet<>();
|
||||||
|
@ -2017,7 +2028,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
String paramName = nextInclude.getParamName();
|
String paramName = nextInclude.getParamName();
|
||||||
if (isNotBlank(paramName)) {
|
if (isNotBlank(paramName)) {
|
||||||
param = theCallingDao.getSearchParamByName(def, paramName);
|
param = mySearchParamRegistry.getSearchParamByName(def, paramName);
|
||||||
} else {
|
} else {
|
||||||
param = null;
|
param = null;
|
||||||
}
|
}
|
||||||
|
@ -2088,6 +2099,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
private void searchForIdsWithAndOr(@Nonnull SearchParameterMap theParams) {
|
private void searchForIdsWithAndOr(@Nonnull SearchParameterMap theParams) {
|
||||||
myParams = theParams;
|
myParams = theParams;
|
||||||
|
|
||||||
|
theParams.clean();
|
||||||
for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : myParams.entrySet()) {
|
for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : myParams.entrySet()) {
|
||||||
String nextParamName = nextParamEntry.getKey();
|
String nextParamName = nextParamEntry.getKey();
|
||||||
List<List<? extends IQueryParameterType>> andOrParams = nextParamEntry.getValue();
|
List<List<? extends IQueryParameterType>> andOrParams = nextParamEntry.getValue();
|
||||||
|
@ -2099,37 +2111,6 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
private void searchForIdsWithAndOr(String theResourceName, String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams) {
|
private void searchForIdsWithAndOr(String theResourceName, String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams) {
|
||||||
|
|
||||||
/*
|
|
||||||
* Filter out
|
|
||||||
*/
|
|
||||||
for (int andListIdx = 0; andListIdx < theAndOrParams.size(); andListIdx++) {
|
|
||||||
List<? extends IQueryParameterType> nextOrList = theAndOrParams.get(andListIdx);
|
|
||||||
|
|
||||||
for (int orListIdx = 0; orListIdx < nextOrList.size(); orListIdx++) {
|
|
||||||
IQueryParameterType nextOr = nextOrList.get(orListIdx);
|
|
||||||
boolean hasNoValue = false;
|
|
||||||
if (nextOr.getMissing() != null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (nextOr instanceof QuantityParam) {
|
|
||||||
if (isBlank(((QuantityParam) nextOr).getValueAsString())) {
|
|
||||||
hasNoValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNoValue) {
|
|
||||||
ourLog.debug("Ignoring empty parameter: {}", theParamName);
|
|
||||||
nextOrList.remove(orListIdx);
|
|
||||||
orListIdx--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextOrList.isEmpty()) {
|
|
||||||
theAndOrParams.remove(andListIdx);
|
|
||||||
andListIdx--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (theAndOrParams.isEmpty()) {
|
if (theAndOrParams.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2288,7 +2269,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
private int myCurrentOffset;
|
private int myCurrentOffset;
|
||||||
private ArrayList<Long> myCurrentPids;
|
private ArrayList<Long> myCurrentPids;
|
||||||
private Long myNext;
|
private Long myNext;
|
||||||
private int myPageSize = myCallingDao.getConfig().getEverythingIncludesFetchPageSize();
|
private int myPageSize = myDaoConfig.getEverythingIncludesFetchPageSize();
|
||||||
|
|
||||||
IncludesIterator(Set<Long> thePidSet) {
|
IncludesIterator(Set<Long> thePidSet) {
|
||||||
myCurrentPids = new ArrayList<>(thePidSet);
|
myCurrentPids = new ArrayList<>(thePidSet);
|
||||||
|
@ -2316,7 +2297,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
myCurrentOffset = end;
|
myCurrentOffset = end;
|
||||||
Collection<Long> pidsToScan = myCurrentPids.subList(start, end);
|
Collection<Long> pidsToScan = myCurrentPids.subList(start, end);
|
||||||
Set<Include> includes = Collections.singleton(new Include("*", true));
|
Set<Include> includes = Collections.singleton(new Include("*", true));
|
||||||
Set<Long> newPids = loadIncludes(myCallingDao, myContext, myEntityManager, pidsToScan, includes, false, myParams.getLastUpdated(), mySearchUuid);
|
Set<Long> newPids = loadIncludes(myContext, myEntityManager, pidsToScan, includes, false, myParams.getLastUpdated(), mySearchUuid);
|
||||||
myCurrentIterator = newPids.iterator();
|
myCurrentIterator = newPids.iterator();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2368,7 +2349,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
// If we don't have a query yet, create one
|
// If we don't have a query yet, create one
|
||||||
if (myResultsIterator == null) {
|
if (myResultsIterator == null) {
|
||||||
if (myMaxResultsToFetch == null) {
|
if (myMaxResultsToFetch == null) {
|
||||||
myMaxResultsToFetch = myCallingDao.getConfig().getFetchSizeDefaultMaximum();
|
myMaxResultsToFetch = myDaoConfig.getFetchSizeDefaultMaximum();
|
||||||
}
|
}
|
||||||
final TypedQuery<Long> query = createQuery(mySort, myMaxResultsToFetch, false);
|
final TypedQuery<Long> query = createQuery(mySort, myMaxResultsToFetch, false);
|
||||||
|
|
||||||
|
@ -2483,7 +2464,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
if (myWrap == null) {
|
if (myWrap == null) {
|
||||||
ourLog.debug("Searching for unique index matches over {} candidate query strings", myUniqueQueryStrings.size());
|
ourLog.debug("Searching for unique index matches over {} candidate query strings", myUniqueQueryStrings.size());
|
||||||
StopWatch sw = new StopWatch();
|
StopWatch sw = new StopWatch();
|
||||||
Collection<Long> resourcePids = myCallingDao.getResourceIndexedCompositeStringUniqueDao().findResourcePidsByQueryStrings(myUniqueQueryStrings);
|
Collection<Long> resourcePids = myResourceIndexedCompositeStringUniqueDao.findResourcePidsByQueryStrings(myUniqueQueryStrings);
|
||||||
ourLog.debug("Found {} unique index matches in {}ms", resourcePids.size(), sw.getMillis());
|
ourLog.debug("Found {} unique index matches in {}ms", resourcePids.size(), sw.getMillis());
|
||||||
myWrap = resourcePids.iterator();
|
myWrap = resourcePids.iterator();
|
||||||
}
|
}
|
||||||
|
@ -2621,10 +2602,10 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
return likeExpression.replace("%", "[%]") + "%";
|
return likeExpression.replace("%", "[%]") + "%";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Predicate createResourceLinkPathPredicate(IDao theCallingDao, FhirContext theContext, String theParamName, From<?, ? extends ResourceLink> theFrom,
|
private Predicate createResourceLinkPathPredicate(FhirContext theContext, String theParamName, From<?, ? extends ResourceLink> theFrom,
|
||||||
String theResourceType) {
|
String theResourceType) {
|
||||||
RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(theResourceType);
|
RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(theResourceType);
|
||||||
RuntimeSearchParam param = theCallingDao.getSearchParamByName(resourceDef, theParamName);
|
RuntimeSearchParam param = mySearchParamRegistry.getSearchParamByName(resourceDef, theParamName);
|
||||||
List<String> path = param.getPathsSplit();
|
List<String> path = param.getPathsSplit();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -8,6 +8,7 @@ import ca.uhn.fhir.model.api.Include;
|
||||||
import ca.uhn.fhir.rest.api.*;
|
import ca.uhn.fhir.rest.api.*;
|
||||||
import ca.uhn.fhir.rest.param.DateParam;
|
import ca.uhn.fhir.rest.param.DateParam;
|
||||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||||
|
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||||
import ca.uhn.fhir.util.ObjectUtil;
|
import ca.uhn.fhir.util.ObjectUtil;
|
||||||
import ca.uhn.fhir.util.UrlUtil;
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
@ -17,6 +18,7 @@ import org.apache.commons.lang3.builder.ToStringStyle;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -40,6 +42,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class SearchParameterMap extends LinkedHashMap<String, List<List<? extends IQueryParameterType>>> {
|
public class SearchParameterMap extends LinkedHashMap<String, List<List<? extends IQueryParameterType>>> {
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParameterMap.class);
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ -336,6 +339,10 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
|
||||||
IQueryParameterType firstValue = nextValuesAnd.get(0);
|
IQueryParameterType firstValue = nextValuesAnd.get(0);
|
||||||
b.append(UrlUtil.escapeUrlParam(nextKey));
|
b.append(UrlUtil.escapeUrlParam(nextKey));
|
||||||
|
|
||||||
|
if (nextKey.equals(Constants.PARAM_HAS)) {
|
||||||
|
b.append(':');
|
||||||
|
}
|
||||||
|
|
||||||
if (firstValue.getMissing() != null) {
|
if (firstValue.getMissing() != null) {
|
||||||
b.append(Constants.PARAMQUALIFIER_MISSING);
|
b.append(Constants.PARAMQUALIFIER_MISSING);
|
||||||
b.append('=');
|
b.append('=');
|
||||||
|
@ -433,6 +440,47 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
|
||||||
return b.toString();
|
return b.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clean() {
|
||||||
|
for (Map.Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : this.entrySet()) {
|
||||||
|
String nextParamName = nextParamEntry.getKey();
|
||||||
|
List<List<? extends IQueryParameterType>> andOrParams = nextParamEntry.getValue();
|
||||||
|
clean(nextParamName, andOrParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Filter out
|
||||||
|
*/
|
||||||
|
private void clean(String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams) {
|
||||||
|
for (int andListIdx = 0; andListIdx < theAndOrParams.size(); andListIdx++) {
|
||||||
|
List<? extends IQueryParameterType> nextOrList = theAndOrParams.get(andListIdx);
|
||||||
|
|
||||||
|
for (int orListIdx = 0; orListIdx < nextOrList.size(); orListIdx++) {
|
||||||
|
IQueryParameterType nextOr = nextOrList.get(orListIdx);
|
||||||
|
boolean hasNoValue = false;
|
||||||
|
if (nextOr.getMissing() != null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (nextOr instanceof QuantityParam) {
|
||||||
|
if (isBlank(((QuantityParam) nextOr).getValueAsString())) {
|
||||||
|
hasNoValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasNoValue) {
|
||||||
|
ourLog.debug("Ignoring empty parameter: {}", theParamName);
|
||||||
|
nextOrList.remove(orListIdx);
|
||||||
|
orListIdx--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextOrList.isEmpty()) {
|
||||||
|
theAndOrParams.remove(andListIdx);
|
||||||
|
andListIdx--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum EverythingModeEnum {
|
public enum EverythingModeEnum {
|
||||||
/*
|
/*
|
||||||
* Don't reorder! We rely on the ordinals
|
* Don't reorder! We rely on the ordinals
|
||||||
|
|
|
@ -43,7 +43,10 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
|
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
|
||||||
import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding;
|
import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import ca.uhn.fhir.util.*;
|
import ca.uhn.fhir.util.FhirTerser;
|
||||||
|
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
||||||
|
import ca.uhn.fhir.util.StopWatch;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
|
@ -81,6 +84,8 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
||||||
@Autowired
|
@Autowired
|
||||||
private ITransactionProcessorVersionAdapter<BUNDLE, BUNDLEENTRY> myVersionAdapter;
|
private ITransactionProcessorVersionAdapter<BUNDLE, BUNDLEENTRY> myVersionAdapter;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private MatchUrlService myMatchUrlService;
|
||||||
|
@Autowired
|
||||||
private DaoRegistry myDaoRegistry;
|
private DaoRegistry myDaoRegistry;
|
||||||
|
|
||||||
private void populateEntryWithOperationOutcome(BaseServerResponseException caughtEx, BUNDLEENTRY nextEntry) {
|
private void populateEntryWithOperationOutcome(BaseServerResponseException caughtEx, BUNDLEENTRY nextEntry) {
|
||||||
|
@ -390,7 +395,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
||||||
requestDetails.setParameters(new HashMap<>());
|
requestDetails.setParameters(new HashMap<>());
|
||||||
if (qIndex != -1) {
|
if (qIndex != -1) {
|
||||||
String params = url.substring(qIndex);
|
String params = url.substring(qIndex);
|
||||||
List<NameValuePair> parameters = BaseHapiFhirDao.translateMatchUrl(params);
|
List<NameValuePair> parameters = myMatchUrlService.translateMatchUrl(params);
|
||||||
for (NameValuePair next : parameters) {
|
for (NameValuePair next : parameters) {
|
||||||
paramValues.put(next.getName(), next.getValue());
|
paramValues.put(next.getName(), next.getValue());
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ import ca.uhn.fhir.jpa.entity.TermCodeSystem;
|
||||||
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
|
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
|
||||||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
import ca.uhn.fhir.jpa.entity.TermConcept;
|
||||||
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
|
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
|
||||||
import ca.uhn.fhir.jpa.util.LogicUtil;
|
import ca.uhn.fhir.jpa.util.LogicUtil;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
|
@ -65,9 +64,6 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3<CodeSys
|
||||||
@Autowired
|
@Autowired
|
||||||
private ITermCodeSystemDao myCsDao;
|
private ITermCodeSystemDao myCsDao;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IHapiTerminologySvc myTerminologySvc;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ValidationSupportChain myValidationSupport;
|
private ValidationSupportChain myValidationSupport;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
package ca.uhn.fhir.jpa.dao.index;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||||
|
import ca.uhn.fhir.jpa.entity.ForcedId;
|
||||||
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class IdHelperService {
|
||||||
|
@Autowired
|
||||||
|
protected IForcedIdDao myForcedIdDao;
|
||||||
|
@Autowired(required = true)
|
||||||
|
private DaoConfig myDaoConfig;
|
||||||
|
|
||||||
|
public void delete(ForcedId forcedId) {
|
||||||
|
myForcedIdDao.delete(forcedId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long translateForcedIdToPid(String theResourceName, String theResourceId) {
|
||||||
|
return translateForcedIdToPids(myDaoConfig, new IdDt(theResourceName, theResourceId), myForcedIdDao).get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Long> translateForcedIdToPids(IIdType theId) {
|
||||||
|
return IdHelperService.translateForcedIdToPids(myDaoConfig, theId, myForcedIdDao);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Long> translateForcedIdToPids(DaoConfig theDaoConfig, IIdType theId, IForcedIdDao theForcedIdDao) {
|
||||||
|
Validate.isTrue(theId.hasIdPart());
|
||||||
|
|
||||||
|
if (theDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY && isValidPid(theId)) {
|
||||||
|
return Collections.singletonList(theId.getIdPartAsLong());
|
||||||
|
} else {
|
||||||
|
List<ForcedId> forcedId;
|
||||||
|
if (theId.hasResourceType()) {
|
||||||
|
forcedId = theForcedIdDao.findByTypeAndForcedId(theId.getResourceType(), theId.getIdPart());
|
||||||
|
} else {
|
||||||
|
forcedId = theForcedIdDao.findByForcedId(theId.getIdPart());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!forcedId.isEmpty()) {
|
||||||
|
List<Long> retVal = new ArrayList<>(forcedId.size());
|
||||||
|
for (ForcedId next : forcedId) {
|
||||||
|
retVal.add(next.getResourcePid());
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
} else {
|
||||||
|
throw new ResourceNotFoundException(theId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String translatePidIdToForcedId(String theResourceType, Long theId) {
|
||||||
|
ForcedId forcedId = myForcedIdDao.findByResourcePid(theId);
|
||||||
|
if (forcedId != null) {
|
||||||
|
return forcedId.getResourceType() + '/' + forcedId.getForcedId();
|
||||||
|
} else {
|
||||||
|
return theResourceType + '/' + theId.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidPid(IIdType theId) {
|
||||||
|
if (theId == null || theId.getIdPart() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String idPart = theId.getIdPart();
|
||||||
|
for (int i = 0; i < idPart.length(); i++) {
|
||||||
|
char nextChar = idPart.charAt(i);
|
||||||
|
if (nextChar < '0' || nextChar > '9') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,58 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.index;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2018 University Health Network
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.entity.BaseHasResource;
|
|
||||||
import ca.uhn.fhir.jpa.entity.IBaseResourceEntity;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTag;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
|
||||||
import ca.uhn.fhir.jpa.dao.ISearchParamExtractor;
|
|
||||||
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
|
|
||||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
|
||||||
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
|
|
||||||
|
|
||||||
public interface IndexingSupport {
|
|
||||||
public DaoConfig getConfig();
|
|
||||||
public ISearchParamExtractor getSearchParamExtractor();
|
|
||||||
public ISearchParamRegistry getSearchParamRegistry();
|
|
||||||
public FhirContext getContext();
|
|
||||||
public EntityManager getEntityManager();
|
|
||||||
public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType);
|
|
||||||
public Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> getResourceTypeToDao();
|
|
||||||
public boolean isLogicalReference(IIdType nextId);
|
|
||||||
public IForcedIdDao getForcedIdDao();
|
|
||||||
public <R extends IBaseResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType);
|
|
||||||
public Long translateForcedIdToPid(String theResourceName, String theResourceId);
|
|
||||||
public String toResourceName(Class<? extends IBaseResource> theResourceType);
|
|
||||||
public IResourceIndexedCompositeStringUniqueDao getResourceIndexedCompositeStringUniqueDao();
|
|
||||||
|
|
||||||
}
|
|
|
@ -20,334 +20,78 @@ package ca.uhn.fhir.jpa.dao.index;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.compare;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBase;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseExtension;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
|
||||||
import org.hl7.fhir.r4.model.CanonicalType;
|
|
||||||
import org.hl7.fhir.r4.model.Reference;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
import ca.uhn.fhir.jpa.dao.*;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import ca.uhn.fhir.jpa.entity.*;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
|
||||||
import ca.uhn.fhir.jpa.dao.PathAndRef;
|
|
||||||
import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ForcedId;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedCompositeStringUnique;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceLink;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
|
||||||
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import java.util.*;
|
||||||
import ca.uhn.fhir.util.FhirTerser;
|
import java.util.Map.Entry;
|
||||||
import ca.uhn.fhir.util.UrlUtil;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.*;
|
||||||
|
|
||||||
public class ResourceIndexedSearchParams {
|
public class ResourceIndexedSearchParams {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceIndexedSearchParams.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceIndexedSearchParams.class);
|
||||||
|
|
||||||
// FIXME rename
|
final Collection<ResourceIndexedSearchParamString> stringParams = new ArrayList<>();
|
||||||
private final IndexingSupport myIndexingService;
|
final Collection<ResourceIndexedSearchParamToken> tokenParams = new HashSet<>();
|
||||||
|
final Collection<ResourceIndexedSearchParamNumber> numberParams = new ArrayList<>();
|
||||||
|
final Collection<ResourceIndexedSearchParamQuantity> quantityParams = new ArrayList<>();
|
||||||
|
final Collection<ResourceIndexedSearchParamDate> dateParams = new ArrayList<>();
|
||||||
|
final Collection<ResourceIndexedSearchParamUri> uriParams = new ArrayList<>();
|
||||||
|
final Collection<ResourceIndexedSearchParamCoords> coordsParams = new ArrayList<>();
|
||||||
|
|
||||||
private final Collection<ResourceIndexedSearchParamString> stringParams;
|
final Collection<ResourceIndexedCompositeStringUnique> compositeStringUniques = new HashSet<>();
|
||||||
private final Collection<ResourceIndexedSearchParamToken> tokenParams;
|
final Collection<ResourceLink> links = new HashSet<>();
|
||||||
private final Collection<ResourceIndexedSearchParamNumber> numberParams;
|
final Set<String> populatedResourceLinkParameters = new HashSet<>();
|
||||||
private final Collection<ResourceIndexedSearchParamQuantity> quantityParams;
|
|
||||||
private final Collection<ResourceIndexedSearchParamDate> dateParams;
|
|
||||||
private final Collection<ResourceIndexedSearchParamUri> uriParams;
|
|
||||||
private final Collection<ResourceIndexedSearchParamCoords> coordsParams;
|
|
||||||
private final Collection<ResourceIndexedCompositeStringUnique> compositeStringUniques;
|
|
||||||
private final Collection<ResourceLink> links;
|
|
||||||
|
|
||||||
private Set<String> populatedResourceLinkParameters = Collections.emptySet();
|
|
||||||
|
|
||||||
public ResourceIndexedSearchParams(IndexingSupport indexingService, ResourceTable theEntity) {
|
public ResourceIndexedSearchParams() {
|
||||||
this.myIndexingService = indexingService;
|
}
|
||||||
|
|
||||||
stringParams = new ArrayList<>();
|
public ResourceIndexedSearchParams(ResourceTable theEntity) {
|
||||||
if (theEntity.isParamsStringPopulated()) {
|
if (theEntity.isParamsStringPopulated()) {
|
||||||
stringParams.addAll(theEntity.getParamsString());
|
stringParams.addAll(theEntity.getParamsString());
|
||||||
}
|
}
|
||||||
tokenParams = new ArrayList<>();
|
|
||||||
if (theEntity.isParamsTokenPopulated()) {
|
if (theEntity.isParamsTokenPopulated()) {
|
||||||
tokenParams.addAll(theEntity.getParamsToken());
|
tokenParams.addAll(theEntity.getParamsToken());
|
||||||
}
|
}
|
||||||
numberParams = new ArrayList<>();
|
|
||||||
if (theEntity.isParamsNumberPopulated()) {
|
if (theEntity.isParamsNumberPopulated()) {
|
||||||
numberParams.addAll(theEntity.getParamsNumber());
|
numberParams.addAll(theEntity.getParamsNumber());
|
||||||
}
|
}
|
||||||
quantityParams = new ArrayList<>();
|
|
||||||
if (theEntity.isParamsQuantityPopulated()) {
|
if (theEntity.isParamsQuantityPopulated()) {
|
||||||
quantityParams.addAll(theEntity.getParamsQuantity());
|
quantityParams.addAll(theEntity.getParamsQuantity());
|
||||||
}
|
}
|
||||||
dateParams = new ArrayList<>();
|
|
||||||
if (theEntity.isParamsDatePopulated()) {
|
if (theEntity.isParamsDatePopulated()) {
|
||||||
dateParams.addAll(theEntity.getParamsDate());
|
dateParams.addAll(theEntity.getParamsDate());
|
||||||
}
|
}
|
||||||
uriParams = new ArrayList<>();
|
|
||||||
if (theEntity.isParamsUriPopulated()) {
|
if (theEntity.isParamsUriPopulated()) {
|
||||||
uriParams.addAll(theEntity.getParamsUri());
|
uriParams.addAll(theEntity.getParamsUri());
|
||||||
}
|
}
|
||||||
coordsParams = new ArrayList<>();
|
|
||||||
if (theEntity.isParamsCoordsPopulated()) {
|
if (theEntity.isParamsCoordsPopulated()) {
|
||||||
coordsParams.addAll(theEntity.getParamsCoords());
|
coordsParams.addAll(theEntity.getParamsCoords());
|
||||||
}
|
}
|
||||||
links = new ArrayList<>();
|
|
||||||
if (theEntity.isHasLinks()) {
|
if (theEntity.isHasLinks()) {
|
||||||
links.addAll(theEntity.getResourceLinks());
|
links.addAll(theEntity.getResourceLinks());
|
||||||
}
|
}
|
||||||
|
|
||||||
compositeStringUniques = new ArrayList<>();
|
|
||||||
if (theEntity.isParamsCompositeStringUniquePresent()) {
|
if (theEntity.isParamsCompositeStringUniquePresent()) {
|
||||||
compositeStringUniques.addAll(theEntity.getParamsCompositeStringUnique());
|
compositeStringUniques.addAll(theEntity.getParamsCompositeStringUnique());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceIndexedSearchParams(IndexingSupport indexingService) {
|
|
||||||
this.myIndexingService = indexingService;
|
|
||||||
|
|
||||||
stringParams = Collections.emptySet();
|
|
||||||
tokenParams = Collections.emptySet();
|
|
||||||
numberParams = Collections.emptySet();
|
|
||||||
quantityParams = Collections.emptySet();
|
|
||||||
dateParams = Collections.emptySet();
|
|
||||||
uriParams = Collections.emptySet();
|
|
||||||
coordsParams = Collections.emptySet();
|
|
||||||
links = Collections.emptySet();
|
|
||||||
compositeStringUniques = Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResourceIndexedSearchParams(IndexingSupport indexingService, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams existingParams) {
|
|
||||||
this.myIndexingService = indexingService;
|
|
||||||
|
|
||||||
stringParams = extractSearchParamStrings(theEntity, theResource);
|
|
||||||
numberParams = extractSearchParamNumber(theEntity, theResource);
|
|
||||||
quantityParams = extractSearchParamQuantity(theEntity, theResource);
|
|
||||||
dateParams = extractSearchParamDates(theEntity, theResource);
|
|
||||||
uriParams = extractSearchParamUri(theEntity, theResource);
|
|
||||||
coordsParams = extractSearchParamCoords(theEntity, theResource);
|
|
||||||
|
|
||||||
ourLog.trace("Storing date indexes: {}", dateParams);
|
|
||||||
|
|
||||||
tokenParams = new HashSet<>();
|
|
||||||
for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theEntity, theResource)) {
|
|
||||||
if (next instanceof ResourceIndexedSearchParamToken) {
|
|
||||||
tokenParams.add((ResourceIndexedSearchParamToken) next);
|
|
||||||
} else {
|
|
||||||
stringParams.add((ResourceIndexedSearchParamString) next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<Entry<String, RuntimeSearchParam>> activeSearchParams = myIndexingService.getSearchParamRegistry().getActiveSearchParams(theEntity.getResourceType()).entrySet();
|
|
||||||
DaoConfig myConfig = indexingService.getConfig();
|
|
||||||
if (myConfig .getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) {
|
|
||||||
findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.STRING, stringParams);
|
|
||||||
findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.NUMBER, numberParams);
|
|
||||||
findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.QUANTITY, quantityParams);
|
|
||||||
findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.DATE, dateParams);
|
|
||||||
findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.URI, uriParams);
|
|
||||||
findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.TOKEN, tokenParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
setUpdatedTime(stringParams, theUpdateTime);
|
|
||||||
setUpdatedTime(numberParams, theUpdateTime);
|
|
||||||
setUpdatedTime(quantityParams, theUpdateTime);
|
|
||||||
setUpdatedTime(dateParams, theUpdateTime);
|
|
||||||
setUpdatedTime(uriParams, theUpdateTime);
|
|
||||||
setUpdatedTime(coordsParams, theUpdateTime);
|
|
||||||
setUpdatedTime(tokenParams, theUpdateTime);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Handle references within the resource that are match URLs, for example references like "Patient?identifier=foo". These match URLs are resolved and replaced with the ID of the
|
|
||||||
* matching resource.
|
|
||||||
*/
|
|
||||||
if (myConfig.isAllowInlineMatchUrlReferences()) {
|
|
||||||
FhirTerser terser = myIndexingService.getContext().newTerser();
|
|
||||||
List<IBaseReference> allRefs = terser.getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
|
|
||||||
for (IBaseReference nextRef : allRefs) {
|
|
||||||
IIdType nextId = nextRef.getReferenceElement();
|
|
||||||
String nextIdText = nextId.getValue();
|
|
||||||
if (nextIdText == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int qmIndex = nextIdText.indexOf('?');
|
|
||||||
if (qmIndex != -1) {
|
|
||||||
for (int i = qmIndex - 1; i >= 0; i--) {
|
|
||||||
if (nextIdText.charAt(i) == '/') {
|
|
||||||
if (i < nextIdText.length() - 1 && nextIdText.charAt(i + 1) == '?') {
|
|
||||||
// Just in case the URL is in the form Patient/?foo=bar
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
nextIdText = nextIdText.substring(i + 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String resourceTypeString = nextIdText.substring(0, nextIdText.indexOf('?')).replace("/", "");
|
|
||||||
RuntimeResourceDefinition matchResourceDef = myIndexingService.getContext().getResourceDefinition(resourceTypeString);
|
|
||||||
if (matchResourceDef == null) {
|
|
||||||
String msg = myIndexingService.getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlInvalidResourceType", nextId.getValue(), resourceTypeString);
|
|
||||||
throw new InvalidRequestException(msg);
|
|
||||||
}
|
|
||||||
Class<? extends IBaseResource> matchResourceType = matchResourceDef.getImplementingClass();
|
|
||||||
Set<Long> matches = myIndexingService.processMatchUrl(nextIdText, matchResourceType);
|
|
||||||
if (matches.isEmpty()) {
|
|
||||||
String msg = indexingService.getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue());
|
|
||||||
throw new ResourceNotFoundException(msg);
|
|
||||||
}
|
|
||||||
if (matches.size() > 1) {
|
|
||||||
String msg = indexingService.getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlMultipleMatches", nextId.getValue());
|
|
||||||
throw new PreconditionFailedException(msg);
|
|
||||||
}
|
|
||||||
Long next = matches.iterator().next();
|
|
||||||
String newId = translatePidIdToForcedId(resourceTypeString, next);
|
|
||||||
ourLog.debug("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId);
|
|
||||||
nextRef.setReference(newId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
links = new HashSet<>();
|
|
||||||
populatedResourceLinkParameters = extractResourceLinks(theEntity, theResource, links, theUpdateTime);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
|
|
||||||
*/
|
|
||||||
for (Iterator<ResourceLink> existingLinkIter = existingParams.getResourceLinks().iterator(); existingLinkIter.hasNext(); ) {
|
|
||||||
ResourceLink nextExisting = existingLinkIter.next();
|
|
||||||
if (links.remove(nextExisting)) {
|
|
||||||
existingLinkIter.remove();
|
|
||||||
links.add(nextExisting);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Handle composites
|
|
||||||
*/
|
|
||||||
compositeStringUniques = extractCompositeStringUniques(theEntity, stringParams, tokenParams, numberParams, quantityParams, dateParams, uriParams, links);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<ResourceLink> getResourceLinks() {
|
public Collection<ResourceLink> getResourceLinks() {
|
||||||
return links;
|
return links;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setParamsOn(ResourceTable theEntity) {
|
||||||
|
|
||||||
protected Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
|
|
||||||
return myIndexingService.getSearchParamExtractor().extractSearchParamCoords(theEntity, theResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
|
|
||||||
return myIndexingService.getSearchParamExtractor().extractSearchParamDates(theEntity, theResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Set<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
|
|
||||||
return myIndexingService.getSearchParamExtractor().extractSearchParamNumber(theEntity, theResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
|
|
||||||
return myIndexingService.getSearchParamExtractor().extractSearchParamQuantity(theEntity, theResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
|
|
||||||
return myIndexingService.getSearchParamExtractor().extractSearchParamStrings(theEntity, theResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
|
|
||||||
return myIndexingService.getSearchParamExtractor().extractSearchParamTokens(theEntity, theResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
|
|
||||||
return myIndexingService.getSearchParamExtractor().extractSearchParamUri(theEntity, theResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <RT extends BaseResourceIndexedSearchParam> void findMissingSearchParams(ResourceTable theEntity, Set<Entry<String, RuntimeSearchParam>> activeSearchParams, RestSearchParameterTypeEnum type,
|
|
||||||
Collection<RT> paramCollection) {
|
|
||||||
for (Entry<String, RuntimeSearchParam> nextEntry : activeSearchParams) {
|
|
||||||
String nextParamName = nextEntry.getKey();
|
|
||||||
if (nextEntry.getValue().getParamType() == type) {
|
|
||||||
boolean haveParam = false;
|
|
||||||
for (BaseResourceIndexedSearchParam nextParam : paramCollection) {
|
|
||||||
if (nextParam.getParamName().equals(nextParamName)) {
|
|
||||||
haveParam = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!haveParam) {
|
|
||||||
BaseResourceIndexedSearchParam param;
|
|
||||||
switch (type) {
|
|
||||||
case DATE:
|
|
||||||
param = new ResourceIndexedSearchParamDate();
|
|
||||||
break;
|
|
||||||
case NUMBER:
|
|
||||||
param = new ResourceIndexedSearchParamNumber();
|
|
||||||
break;
|
|
||||||
case QUANTITY:
|
|
||||||
param = new ResourceIndexedSearchParamQuantity();
|
|
||||||
break;
|
|
||||||
case STRING:
|
|
||||||
param = new ResourceIndexedSearchParamString()
|
|
||||||
.setDaoConfig(myIndexingService.getConfig());
|
|
||||||
break;
|
|
||||||
case TOKEN:
|
|
||||||
param = new ResourceIndexedSearchParamToken();
|
|
||||||
break;
|
|
||||||
case URI:
|
|
||||||
param = new ResourceIndexedSearchParamUri();
|
|
||||||
break;
|
|
||||||
case COMPOSITE:
|
|
||||||
case HAS:
|
|
||||||
case REFERENCE:
|
|
||||||
default:
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
param.setResource(theEntity);
|
|
||||||
param.setMissing(true);
|
|
||||||
param.setParamName(nextParamName);
|
|
||||||
paramCollection.add((RT) param);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setParams(ResourceTable theEntity) {
|
|
||||||
theEntity.setParamsString(stringParams);
|
theEntity.setParamsString(stringParams);
|
||||||
theEntity.setParamsStringPopulated(stringParams.isEmpty() == false);
|
theEntity.setParamsStringPopulated(stringParams.isEmpty() == false);
|
||||||
theEntity.setParamsToken(tokenParams);
|
theEntity.setParamsToken(tokenParams);
|
||||||
|
@ -367,88 +111,22 @@ public class ResourceIndexedSearchParams {
|
||||||
theEntity.setHasLinks(links.isEmpty() == false);
|
theEntity.setHasLinks(links.isEmpty() == false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUpdatedTime(Date theUpdateTime) {
|
||||||
private Set<ResourceIndexedCompositeStringUnique> extractCompositeStringUniques(ResourceTable theEntity, Collection<ResourceIndexedSearchParamString> theStringParams, Collection<ResourceIndexedSearchParamToken> theTokenParams, Collection<ResourceIndexedSearchParamNumber> theNumberParams, Collection<ResourceIndexedSearchParamQuantity> theQuantityParams, Collection<ResourceIndexedSearchParamDate> theDateParams, Collection<ResourceIndexedSearchParamUri> theUriParams, Collection<ResourceLink> theLinks) {
|
setUpdatedTime(stringParams, theUpdateTime);
|
||||||
Set<ResourceIndexedCompositeStringUnique> compositeStringUniques;
|
setUpdatedTime(numberParams, theUpdateTime);
|
||||||
compositeStringUniques = new HashSet<>();
|
setUpdatedTime(quantityParams, theUpdateTime);
|
||||||
List<JpaRuntimeSearchParam> uniqueSearchParams = myIndexingService.getSearchParamRegistry().getActiveUniqueSearchParams(theEntity.getResourceType());
|
setUpdatedTime(dateParams, theUpdateTime);
|
||||||
for (JpaRuntimeSearchParam next : uniqueSearchParams) {
|
setUpdatedTime(uriParams, theUpdateTime);
|
||||||
|
setUpdatedTime(coordsParams, theUpdateTime);
|
||||||
List<List<String>> partsChoices = new ArrayList<>();
|
setUpdatedTime(tokenParams, theUpdateTime);
|
||||||
|
|
||||||
for (RuntimeSearchParam nextCompositeOf : next.getCompositeOf()) {
|
|
||||||
Collection<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null;
|
|
||||||
Collection<ResourceLink> linksForCompositePart = null;
|
|
||||||
Collection<String> linksForCompositePartWantPaths = null;
|
|
||||||
switch (nextCompositeOf.getParamType()) {
|
|
||||||
case NUMBER:
|
|
||||||
paramsListForCompositePart = theNumberParams;
|
|
||||||
break;
|
|
||||||
case DATE:
|
|
||||||
paramsListForCompositePart = theDateParams;
|
|
||||||
break;
|
|
||||||
case STRING:
|
|
||||||
paramsListForCompositePart = theStringParams;
|
|
||||||
break;
|
|
||||||
case TOKEN:
|
|
||||||
paramsListForCompositePart = theTokenParams;
|
|
||||||
break;
|
|
||||||
case REFERENCE:
|
|
||||||
linksForCompositePart = theLinks;
|
|
||||||
linksForCompositePartWantPaths = new HashSet<>();
|
|
||||||
linksForCompositePartWantPaths.addAll(nextCompositeOf.getPathsSplit());
|
|
||||||
break;
|
|
||||||
case QUANTITY:
|
|
||||||
paramsListForCompositePart = theQuantityParams;
|
|
||||||
break;
|
|
||||||
case URI:
|
|
||||||
paramsListForCompositePart = theUriParams;
|
|
||||||
break;
|
|
||||||
case COMPOSITE:
|
|
||||||
case HAS:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayList<String> nextChoicesList = new ArrayList<>();
|
private void setUpdatedTime(Collection<? extends BaseResourceIndexedSearchParam> theParams, Date theUpdateTime) {
|
||||||
partsChoices.add(nextChoicesList);
|
for (BaseResourceIndexedSearchParam nextSearchParam : theParams) {
|
||||||
|
nextSearchParam.setUpdated(theUpdateTime);
|
||||||
String key = UrlUtil.escapeUrlParam(nextCompositeOf.getName());
|
|
||||||
if (paramsListForCompositePart != null) {
|
|
||||||
for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) {
|
|
||||||
if (nextParam.getParamName().equals(nextCompositeOf.getName())) {
|
|
||||||
IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType();
|
|
||||||
String value = nextParamAsClientParam.getValueAsQueryToken(myIndexingService.getContext());
|
|
||||||
if (isNotBlank(value)) {
|
|
||||||
value = UrlUtil.escapeUrlParam(value);
|
|
||||||
nextChoicesList.add(key + "=" + value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (linksForCompositePart != null) {
|
|
||||||
for (ResourceLink nextLink : linksForCompositePart) {
|
|
||||||
if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) {
|
|
||||||
String value = nextLink.getTargetResource().getIdDt().toUnqualifiedVersionless().getValue();
|
|
||||||
if (isNotBlank(value)) {
|
|
||||||
value = UrlUtil.escapeUrlParam(value);
|
|
||||||
nextChoicesList.add(key + "=" + value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> queryStringsToPopulate = extractCompositeStringUniquesValueChains(theEntity.getResourceType(), partsChoices);
|
|
||||||
|
|
||||||
for (String nextQueryString : queryStringsToPopulate) {
|
|
||||||
if (isNotBlank(nextQueryString)) {
|
|
||||||
compositeStringUniques.add(new ResourceIndexedCompositeStringUnique(theEntity, nextQueryString));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return compositeStringUniques;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is used to create a set of all possible combinations of
|
* This method is used to create a set of all possible combinations of
|
||||||
|
@ -531,342 +209,163 @@ public class ResourceIndexedSearchParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Returns a set containing all of the parameter names that
|
|
||||||
* were found to have a value
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected Set<String> extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Collection<ResourceLink> theLinks, Date theUpdateTime) {
|
|
||||||
HashSet<String> retVal = new HashSet<>();
|
|
||||||
String resourceType = theEntity.getResourceType();
|
|
||||||
|
|
||||||
/*
|
void calculateHashes(Collection<? extends BaseResourceIndexedSearchParam> theStringParams) {
|
||||||
* For now we don't try to load any of the links in a bundle if it's the actual bundle we're storing..
|
|
||||||
*/
|
|
||||||
if (theResource instanceof IBaseBundle) {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, RuntimeSearchParam> searchParams = myIndexingService.getSearchParamRegistry().getActiveSearchParams(myIndexingService.toResourceName(theResource.getClass()));
|
|
||||||
for (RuntimeSearchParam nextSpDef : searchParams.values()) {
|
|
||||||
|
|
||||||
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String nextPathsUnsplit = nextSpDef.getPath();
|
|
||||||
if (isBlank(nextPathsUnsplit)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean multiType = false;
|
|
||||||
if (nextPathsUnsplit.endsWith("[x]")) {
|
|
||||||
multiType = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<PathAndRef> refs = myIndexingService.getSearchParamExtractor().extractResourceLinks(theResource, nextSpDef);
|
|
||||||
for (PathAndRef nextPathAndRef : refs) {
|
|
||||||
Object nextObject = nextPathAndRef.getRef();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A search parameter on an extension field that contains
|
|
||||||
* references should index those references
|
|
||||||
*/
|
|
||||||
if (nextObject instanceof IBaseExtension<?, ?>) {
|
|
||||||
nextObject = ((IBaseExtension<?, ?>) nextObject).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextObject instanceof CanonicalType) {
|
|
||||||
nextObject = new Reference(((CanonicalType) nextObject).getValueAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
IIdType nextId;
|
|
||||||
if (nextObject instanceof IBaseReference) {
|
|
||||||
IBaseReference nextValue = (IBaseReference) nextObject;
|
|
||||||
if (nextValue.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
nextId = nextValue.getReferenceElement();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This can only really happen if the DAO is being called
|
|
||||||
* programatically with a Bundle (not through the FHIR REST API)
|
|
||||||
* but Smile does this
|
|
||||||
*/
|
|
||||||
if (nextId.isEmpty() && nextValue.getResource() != null) {
|
|
||||||
nextId = nextValue.getResource().getIdElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextId.isEmpty() || nextId.getValue().startsWith("#")) {
|
|
||||||
// This is a blank or contained resource reference
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (nextObject instanceof IBaseResource) {
|
|
||||||
nextId = ((IBaseResource) nextObject).getIdElement();
|
|
||||||
if (nextId == null || nextId.hasIdPart() == false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (myIndexingService.getContext().getElementDefinition((Class<? extends IBase>) nextObject.getClass()).getName().equals("uri")) {
|
|
||||||
continue;
|
|
||||||
} else if (resourceType.equals("Consent") && nextPathAndRef.getPath().equals("Consent.source")) {
|
|
||||||
// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
if (!multiType) {
|
|
||||||
if (nextSpDef.getName().equals("sourceuri")) {
|
|
||||||
continue; // TODO: disable this eventually - ConceptMap:sourceuri is of type reference but points to a URI
|
|
||||||
}
|
|
||||||
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
retVal.add(nextSpDef.getName());
|
|
||||||
|
|
||||||
if (myIndexingService.isLogicalReference(nextId)) {
|
|
||||||
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
|
|
||||||
if (theLinks.add(resourceLink)) {
|
|
||||||
ourLog.debug("Indexing remote resource reference URL: {}", nextId);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String baseUrl = nextId.getBaseUrl();
|
|
||||||
String typeString = nextId.getResourceType();
|
|
||||||
if (isBlank(typeString)) {
|
|
||||||
throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource type - " + nextId.getValue());
|
|
||||||
}
|
|
||||||
RuntimeResourceDefinition resourceDefinition;
|
|
||||||
try {
|
|
||||||
resourceDefinition = myIndexingService.getContext().getResourceDefinition(typeString);
|
|
||||||
} catch (DataFormatException e) {
|
|
||||||
throw new InvalidRequestException(
|
|
||||||
"Invalid resource reference found at path[" + nextPathsUnsplit + "] - Resource type is unknown or not supported on this server - " + nextId.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNotBlank(baseUrl)) {
|
|
||||||
if (!myIndexingService.getConfig().getTreatBaseUrlsAsLocal().contains(baseUrl) && !myIndexingService.getConfig().isAllowExternalReferences()) {
|
|
||||||
String msg = myIndexingService.getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "externalReferenceNotAllowed", nextId.getValue());
|
|
||||||
throw new InvalidRequestException(msg);
|
|
||||||
} else {
|
|
||||||
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
|
|
||||||
if (theLinks.add(resourceLink)) {
|
|
||||||
ourLog.debug("Indexing remote resource reference URL: {}", nextId);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
|
|
||||||
String id = nextId.getIdPart();
|
|
||||||
if (StringUtils.isBlank(id)) {
|
|
||||||
throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource ID - " + nextId.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
IFhirResourceDao<?> dao = myIndexingService.getDao(type);
|
|
||||||
if (dao == null) {
|
|
||||||
StringBuilder b = new StringBuilder();
|
|
||||||
b.append("This server (version ");
|
|
||||||
b.append(myIndexingService.getContext().getVersion().getVersion());
|
|
||||||
b.append(") is not able to handle resources of type[");
|
|
||||||
b.append(nextId.getResourceType());
|
|
||||||
b.append("] - Valid resource types for this server: ");
|
|
||||||
b.append(myIndexingService.getResourceTypeToDao().keySet().toString());
|
|
||||||
|
|
||||||
throw new InvalidRequestException(b.toString());
|
|
||||||
}
|
|
||||||
Long valueOf;
|
|
||||||
try {
|
|
||||||
valueOf = myIndexingService.translateForcedIdToPid(typeString, id);
|
|
||||||
} catch (ResourceNotFoundException e) {
|
|
||||||
if (myIndexingService.getConfig().isEnforceReferentialIntegrityOnWrite() == false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
RuntimeResourceDefinition missingResourceDef = myIndexingService.getContext().getResourceDefinition(type);
|
|
||||||
String resName = missingResourceDef.getName();
|
|
||||||
|
|
||||||
if (myIndexingService.getConfig().isAutoCreatePlaceholderReferenceTargets()) {
|
|
||||||
IBaseResource newResource = missingResourceDef.newInstance();
|
|
||||||
newResource.setId(resName + "/" + id);
|
|
||||||
IFhirResourceDao<IBaseResource> placeholderResourceDao = (IFhirResourceDao<IBaseResource>) myIndexingService.getDao(newResource.getClass());
|
|
||||||
ourLog.debug("Automatically creating empty placeholder resource: {}", newResource.getIdElement().getValue());
|
|
||||||
valueOf = placeholderResourceDao.update(newResource).getEntity().getId();
|
|
||||||
} else {
|
|
||||||
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ResourceTable target = myIndexingService.getEntityManager().find(ResourceTable.class, valueOf);
|
|
||||||
RuntimeResourceDefinition targetResourceDef = myIndexingService.getContext().getResourceDefinition(type);
|
|
||||||
if (target == null) {
|
|
||||||
String resName = targetResourceDef.getName();
|
|
||||||
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!typeString.equals(target.getResourceType())) {
|
|
||||||
throw new UnprocessableEntityException(
|
|
||||||
"Resource contains reference to " + nextId.getValue() + " but resource with ID " + nextId.getIdPart() + " is actually of type " + target.getResourceType());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.getDeleted() != null) {
|
|
||||||
String resName = targetResourceDef.getName();
|
|
||||||
throw new InvalidRequestException("Resource " + resName + "/" + id + " is deleted, specified in path: " + nextPathsUnsplit);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextSpDef.getTargets() != null && !nextSpDef.getTargets().contains(typeString)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, target, theUpdateTime);
|
|
||||||
theLinks.add(resourceLink);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
theEntity.setHasLinks(theLinks.size() > 0);
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUpdatedTime(Collection<? extends BaseResourceIndexedSearchParam> theParams, Date theUpdateTime) {
|
|
||||||
for (BaseResourceIndexedSearchParam nextSearchParam : theParams) {
|
|
||||||
nextSearchParam.setUpdated(theUpdateTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String translatePidIdToForcedId(String theResourceType, Long theId) {
|
|
||||||
ForcedId forcedId = myIndexingService.getForcedIdDao().findByResourcePid(theId);
|
|
||||||
if (forcedId != null) {
|
|
||||||
return forcedId.getResourceType() + '/' + forcedId.getForcedId();
|
|
||||||
} else {
|
|
||||||
return theResourceType + '/' + theId.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeCommon(ResourceTable theEntity, ResourceIndexedSearchParams existingParams) {
|
|
||||||
EntityManager myEntityManager = myIndexingService.getEntityManager();
|
|
||||||
|
|
||||||
calculateHashes(stringParams);
|
|
||||||
for (ResourceIndexedSearchParamString next : removeCommon(existingParams.stringParams, stringParams)) {
|
|
||||||
next.setDaoConfig(myIndexingService.getConfig());
|
|
||||||
myEntityManager .remove(next);
|
|
||||||
theEntity.getParamsString().remove(next);
|
|
||||||
}
|
|
||||||
for (ResourceIndexedSearchParamString next : removeCommon(stringParams, existingParams.stringParams)) {
|
|
||||||
myEntityManager.persist(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateHashes(tokenParams);
|
|
||||||
for (ResourceIndexedSearchParamToken next : removeCommon(existingParams.tokenParams, tokenParams)) {
|
|
||||||
myEntityManager.remove(next);
|
|
||||||
theEntity.getParamsToken().remove(next);
|
|
||||||
}
|
|
||||||
for (ResourceIndexedSearchParamToken next : removeCommon(tokenParams, existingParams.tokenParams)) {
|
|
||||||
myEntityManager.persist(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateHashes(numberParams);
|
|
||||||
for (ResourceIndexedSearchParamNumber next : removeCommon(existingParams.numberParams, numberParams)) {
|
|
||||||
myEntityManager.remove(next);
|
|
||||||
theEntity.getParamsNumber().remove(next);
|
|
||||||
}
|
|
||||||
for (ResourceIndexedSearchParamNumber next : removeCommon(numberParams, existingParams.numberParams)) {
|
|
||||||
myEntityManager.persist(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateHashes(quantityParams);
|
|
||||||
for (ResourceIndexedSearchParamQuantity next : removeCommon(existingParams.quantityParams, quantityParams)) {
|
|
||||||
myEntityManager.remove(next);
|
|
||||||
theEntity.getParamsQuantity().remove(next);
|
|
||||||
}
|
|
||||||
for (ResourceIndexedSearchParamQuantity next : removeCommon(quantityParams, existingParams.quantityParams)) {
|
|
||||||
myEntityManager.persist(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store date SP's
|
|
||||||
calculateHashes(dateParams);
|
|
||||||
for (ResourceIndexedSearchParamDate next : removeCommon(existingParams.dateParams, dateParams)) {
|
|
||||||
myEntityManager.remove(next);
|
|
||||||
theEntity.getParamsDate().remove(next);
|
|
||||||
}
|
|
||||||
for (ResourceIndexedSearchParamDate next : removeCommon(dateParams, existingParams.dateParams)) {
|
|
||||||
myEntityManager.persist(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store URI SP's
|
|
||||||
calculateHashes(uriParams);
|
|
||||||
for (ResourceIndexedSearchParamUri next : removeCommon(existingParams.uriParams, uriParams)) {
|
|
||||||
myEntityManager.remove(next);
|
|
||||||
theEntity.getParamsUri().remove(next);
|
|
||||||
}
|
|
||||||
for (ResourceIndexedSearchParamUri next : removeCommon(uriParams, existingParams.uriParams)) {
|
|
||||||
myEntityManager.persist(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store Coords SP's
|
|
||||||
calculateHashes(coordsParams);
|
|
||||||
for (ResourceIndexedSearchParamCoords next : removeCommon(existingParams.coordsParams, coordsParams)) {
|
|
||||||
myEntityManager.remove(next);
|
|
||||||
theEntity.getParamsCoords().remove(next);
|
|
||||||
}
|
|
||||||
for (ResourceIndexedSearchParamCoords next : removeCommon(coordsParams, existingParams.coordsParams)) {
|
|
||||||
myEntityManager.persist(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store resource links
|
|
||||||
for (ResourceLink next : removeCommon(existingParams.links, links)) {
|
|
||||||
myEntityManager.remove(next);
|
|
||||||
theEntity.getResourceLinks().remove(next);
|
|
||||||
}
|
|
||||||
for (ResourceLink next : removeCommon(links, existingParams.links)) {
|
|
||||||
myEntityManager.persist(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure links are indexed
|
|
||||||
theEntity.setResourceLinks(links);
|
|
||||||
|
|
||||||
// Store composite string uniques
|
|
||||||
if (myIndexingService.getConfig().isUniqueIndexesEnabled()) {
|
|
||||||
for (ResourceIndexedCompositeStringUnique next : removeCommon(existingParams.compositeStringUniques, compositeStringUniques)) {
|
|
||||||
ourLog.debug("Removing unique index: {}", next);
|
|
||||||
myEntityManager.remove(next);
|
|
||||||
theEntity.getParamsCompositeStringUnique().remove(next);
|
|
||||||
}
|
|
||||||
for (ResourceIndexedCompositeStringUnique next : removeCommon(compositeStringUniques, existingParams.compositeStringUniques)) {
|
|
||||||
if (myIndexingService.getConfig().isUniqueIndexesCheckedBeforeSave()) {
|
|
||||||
ResourceIndexedCompositeStringUnique existing = myIndexingService.getResourceIndexedCompositeStringUniqueDao().findByQueryString(next.getIndexString());
|
|
||||||
if (existing != null) {
|
|
||||||
String msg = myIndexingService.getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "uniqueIndexConflictFailure", theEntity.getResourceType(), next.getIndexString(), existing.getResource().getIdDt().toUnqualifiedVersionless().getValue());
|
|
||||||
throw new PreconditionFailedException(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ourLog.debug("Persisting unique index: {}", next);
|
|
||||||
myEntityManager.persist(next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void calculateHashes(Collection<? extends BaseResourceIndexedSearchParam> theStringParams) {
|
|
||||||
for (BaseResourceIndexedSearchParam next : theStringParams) {
|
for (BaseResourceIndexedSearchParam next : theStringParams) {
|
||||||
next.calculateHashes();
|
next.calculateHashes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> Collection<T> removeCommon(Collection<T> theInput, Collection<T> theToRemove) {
|
|
||||||
assert theInput != theToRemove;
|
|
||||||
|
|
||||||
if (theInput.isEmpty()) {
|
|
||||||
return theInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<T> retVal = new ArrayList<>(theInput);
|
|
||||||
retVal.removeAll(theToRemove);
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getPopulatedResourceLinkParameters() {
|
public Set<String> getPopulatedResourceLinkParameters() {
|
||||||
return populatedResourceLinkParameters;
|
return populatedResourceLinkParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean matchParam(String theResourceName, String theParamName, RuntimeSearchParam paramDef, IQueryParameterType theParam) {
|
||||||
|
if (paramDef == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Collection<? extends BaseResourceIndexedSearchParam> resourceParams;
|
||||||
|
switch (paramDef.getParamType()) {
|
||||||
|
case TOKEN:
|
||||||
|
resourceParams = tokenParams;
|
||||||
|
break;
|
||||||
|
case QUANTITY:
|
||||||
|
resourceParams = quantityParams;
|
||||||
|
break;
|
||||||
|
case STRING:
|
||||||
|
resourceParams = stringParams;
|
||||||
|
break;
|
||||||
|
case NUMBER:
|
||||||
|
resourceParams = numberParams;
|
||||||
|
break;
|
||||||
|
case URI:
|
||||||
|
resourceParams = uriParams;
|
||||||
|
break;
|
||||||
|
case DATE:
|
||||||
|
resourceParams = dateParams;
|
||||||
|
break;
|
||||||
|
case REFERENCE:
|
||||||
|
return matchResourceLinks(theResourceName, theParamName, theParam);
|
||||||
|
case COMPOSITE:
|
||||||
|
case HAS:
|
||||||
|
case SPECIAL:
|
||||||
|
default:
|
||||||
|
resourceParams = null;
|
||||||
|
}
|
||||||
|
if (resourceParams == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Predicate<BaseResourceIndexedSearchParam> namedParamPredicate = param ->
|
||||||
|
param.getParamName().equalsIgnoreCase(theParamName) &&
|
||||||
|
param.matches(theParam);
|
||||||
|
|
||||||
|
return resourceParams.stream().anyMatch(namedParamPredicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matchResourceLinks(String theResourceName, String theParamName, IQueryParameterType theParam) {
|
||||||
|
ReferenceParam reference = (ReferenceParam)theParam;
|
||||||
|
|
||||||
|
Predicate<ResourceLink> namedParamPredicate = resourceLink ->
|
||||||
|
resourceLinkMatches(theResourceName, resourceLink, theParamName)
|
||||||
|
&& resourceIdMatches(resourceLink, reference);
|
||||||
|
|
||||||
|
return links.stream().anyMatch(namedParamPredicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean resourceIdMatches(ResourceLink theResourceLink, ReferenceParam theReference) {
|
||||||
|
ResourceTable target = theResourceLink.getTargetResource();
|
||||||
|
IdDt idDt = target.getIdDt();
|
||||||
|
if (idDt.isIdPartValidLong()) {
|
||||||
|
return theReference.getIdPartAsLong() == idDt.getIdPartAsLong();
|
||||||
|
} else {
|
||||||
|
ForcedId forcedId = target.getForcedId();
|
||||||
|
if (forcedId != null) {
|
||||||
|
return forcedId.getForcedId().equals(theReference.getValue());
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean resourceLinkMatches(String theResourceName, ResourceLink theResourceLink, String theParamName) {
|
||||||
|
return theResourceLink.getTargetResource().getResourceType().equalsIgnoreCase(theParamName) ||
|
||||||
|
theResourceLink.getSourcePath().equalsIgnoreCase(theResourceName+"."+theParamName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ResourceIndexedSearchParams{" +
|
||||||
|
"stringParams=" + stringParams +
|
||||||
|
", tokenParams=" + tokenParams +
|
||||||
|
", numberParams=" + numberParams +
|
||||||
|
", quantityParams=" + quantityParams +
|
||||||
|
", dateParams=" + dateParams +
|
||||||
|
", uriParams=" + uriParams +
|
||||||
|
", coordsParams=" + coordsParams +
|
||||||
|
", compositeStringUniques=" + compositeStringUniques +
|
||||||
|
", links=" + links +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
void findMissingSearchParams(DaoConfig theDaoConfig, ResourceTable theEntity, Set<Entry<String, RuntimeSearchParam>> theActiveSearchParams) {
|
||||||
|
findMissingSearchParams(theDaoConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.STRING, stringParams);
|
||||||
|
findMissingSearchParams(theDaoConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.NUMBER, numberParams);
|
||||||
|
findMissingSearchParams(theDaoConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, quantityParams);
|
||||||
|
findMissingSearchParams(theDaoConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.DATE, dateParams);
|
||||||
|
findMissingSearchParams(theDaoConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.URI, uriParams);
|
||||||
|
findMissingSearchParams(theDaoConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.TOKEN, tokenParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <RT extends BaseResourceIndexedSearchParam> void findMissingSearchParams(DaoConfig theDaoConfig, ResourceTable theEntity, Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams, RestSearchParameterTypeEnum type,
|
||||||
|
Collection<RT> paramCollection) {
|
||||||
|
for (Map.Entry<String, RuntimeSearchParam> nextEntry : activeSearchParams) {
|
||||||
|
String nextParamName = nextEntry.getKey();
|
||||||
|
if (nextEntry.getValue().getParamType() == type) {
|
||||||
|
boolean haveParam = false;
|
||||||
|
for (BaseResourceIndexedSearchParam nextParam : paramCollection) {
|
||||||
|
if (nextParam.getParamName().equals(nextParamName)) {
|
||||||
|
haveParam = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!haveParam) {
|
||||||
|
BaseResourceIndexedSearchParam param;
|
||||||
|
switch (type) {
|
||||||
|
case DATE:
|
||||||
|
param = new ResourceIndexedSearchParamDate();
|
||||||
|
break;
|
||||||
|
case NUMBER:
|
||||||
|
param = new ResourceIndexedSearchParamNumber();
|
||||||
|
break;
|
||||||
|
case QUANTITY:
|
||||||
|
param = new ResourceIndexedSearchParamQuantity();
|
||||||
|
break;
|
||||||
|
case STRING:
|
||||||
|
param = new ResourceIndexedSearchParamString()
|
||||||
|
.setDaoConfig(theDaoConfig);
|
||||||
|
break;
|
||||||
|
case TOKEN:
|
||||||
|
param = new ResourceIndexedSearchParamToken();
|
||||||
|
break;
|
||||||
|
case URI:
|
||||||
|
param = new ResourceIndexedSearchParamUri();
|
||||||
|
break;
|
||||||
|
case COMPOSITE:
|
||||||
|
case HAS:
|
||||||
|
case REFERENCE:
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
param.setResource(theEntity);
|
||||||
|
param.setMissing(true);
|
||||||
|
param.setParamName(nextParamName);
|
||||||
|
paramCollection.add((RT) param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,587 @@
|
||||||
|
package ca.uhn.fhir.jpa.dao.index;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
|
import ca.uhn.fhir.jpa.dao.*;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
|
||||||
|
import ca.uhn.fhir.jpa.entity.*;
|
||||||
|
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
|
||||||
|
import ca.uhn.fhir.jpa.dao.MatchUrlService;
|
||||||
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
|
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
|
import ca.uhn.fhir.util.FhirTerser;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.hl7.fhir.instance.model.api.*;
|
||||||
|
import org.hl7.fhir.r4.model.CanonicalType;
|
||||||
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.PersistenceContext;
|
||||||
|
import javax.persistence.PersistenceContextType;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Lazy
|
||||||
|
public class SearchParamExtractorService {
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamExtractorService.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DaoConfig myDaoConfig;
|
||||||
|
@Autowired
|
||||||
|
private FhirContext myContext;
|
||||||
|
@Autowired
|
||||||
|
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
|
||||||
|
@Autowired
|
||||||
|
private ISearchParamExtractor mySearchParamExtractor;
|
||||||
|
@Autowired
|
||||||
|
private ISearchParamRegistry mySearchParamRegistry;
|
||||||
|
@Autowired
|
||||||
|
private IdHelperService myIdHelperService;
|
||||||
|
@Autowired
|
||||||
|
private DaoRegistry myDaoRegistry;
|
||||||
|
@Autowired
|
||||||
|
private MatchUrlService myMatchUrlService;
|
||||||
|
|
||||||
|
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||||
|
protected EntityManager myEntityManager;
|
||||||
|
|
||||||
|
|
||||||
|
public void populateFromResource(ResourceIndexedSearchParams theParams, IDao theCallingDao, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams existingParams) {
|
||||||
|
extractFromResource(theParams, theEntity, theResource);
|
||||||
|
|
||||||
|
Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();
|
||||||
|
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) {
|
||||||
|
theParams.findMissingSearchParams(myDaoConfig, theEntity, activeSearchParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
theParams.setUpdatedTime(theUpdateTime);
|
||||||
|
|
||||||
|
extractInlineReferences(theResource);
|
||||||
|
|
||||||
|
extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, true);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
|
||||||
|
*/
|
||||||
|
for (Iterator<ResourceLink> existingLinkIter = existingParams.getResourceLinks().iterator(); existingLinkIter.hasNext(); ) {
|
||||||
|
ResourceLink nextExisting = existingLinkIter.next();
|
||||||
|
if (theParams.links.remove(nextExisting)) {
|
||||||
|
existingLinkIter.remove();
|
||||||
|
theParams.links.add(nextExisting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle composites
|
||||||
|
*/
|
||||||
|
extractCompositeStringUniques(theEntity, theParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void extractFromResource(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource) {
|
||||||
|
theParams.stringParams.addAll(extractSearchParamStrings(theEntity, theResource));
|
||||||
|
theParams.numberParams.addAll(extractSearchParamNumber(theEntity, theResource));
|
||||||
|
theParams.quantityParams.addAll(extractSearchParamQuantity(theEntity, theResource));
|
||||||
|
theParams.dateParams.addAll(extractSearchParamDates(theEntity, theResource));
|
||||||
|
theParams.uriParams.addAll(extractSearchParamUri(theEntity, theResource));
|
||||||
|
theParams.coordsParams.addAll(extractSearchParamCoords(theEntity, theResource));
|
||||||
|
|
||||||
|
ourLog.trace("Storing date indexes: {}", theParams.dateParams);
|
||||||
|
|
||||||
|
for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theEntity, theResource)) {
|
||||||
|
if (next instanceof ResourceIndexedSearchParamToken) {
|
||||||
|
theParams.tokenParams.add((ResourceIndexedSearchParamToken) next);
|
||||||
|
} else {
|
||||||
|
theParams.stringParams.add((ResourceIndexedSearchParamString) next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle references within the resource that are match URLs, for example references like "Patient?identifier=foo". These match URLs are resolved and replaced with the ID of the
|
||||||
|
* matching resource.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void extractInlineReferences(IBaseResource theResource) {
|
||||||
|
if (!myDaoConfig.isAllowInlineMatchUrlReferences()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FhirTerser terser = myContext.newTerser();
|
||||||
|
List<IBaseReference> allRefs = terser.getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
|
||||||
|
for (IBaseReference nextRef : allRefs) {
|
||||||
|
IIdType nextId = nextRef.getReferenceElement();
|
||||||
|
String nextIdText = nextId.getValue();
|
||||||
|
if (nextIdText == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int qmIndex = nextIdText.indexOf('?');
|
||||||
|
if (qmIndex != -1) {
|
||||||
|
for (int i = qmIndex - 1; i >= 0; i--) {
|
||||||
|
if (nextIdText.charAt(i) == '/') {
|
||||||
|
if (i < nextIdText.length() - 1 && nextIdText.charAt(i + 1) == '?') {
|
||||||
|
// Just in case the URL is in the form Patient/?foo=bar
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
nextIdText = nextIdText.substring(i + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String resourceTypeString = nextIdText.substring(0, nextIdText.indexOf('?')).replace("/", "");
|
||||||
|
RuntimeResourceDefinition matchResourceDef = myContext.getResourceDefinition(resourceTypeString);
|
||||||
|
if (matchResourceDef == null) {
|
||||||
|
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlInvalidResourceType", nextId.getValue(), resourceTypeString);
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
Class<? extends IBaseResource> matchResourceType = matchResourceDef.getImplementingClass();
|
||||||
|
Set<Long> matches = myMatchUrlService.processMatchUrl(nextIdText, matchResourceType);
|
||||||
|
if (matches.isEmpty()) {
|
||||||
|
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue());
|
||||||
|
throw new ResourceNotFoundException(msg);
|
||||||
|
}
|
||||||
|
if (matches.size() > 1) {
|
||||||
|
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlMultipleMatches", nextId.getValue());
|
||||||
|
throw new PreconditionFailedException(msg);
|
||||||
|
}
|
||||||
|
Long next = matches.iterator().next();
|
||||||
|
String newId = myIdHelperService.translatePidIdToForcedId(resourceTypeString, next);
|
||||||
|
ourLog.debug("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId);
|
||||||
|
nextRef.setReference(newId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
|
||||||
|
return mySearchParamExtractor.extractSearchParamCoords(theEntity, theResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
|
||||||
|
return mySearchParamExtractor.extractSearchParamDates(theEntity, theResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
|
||||||
|
return mySearchParamExtractor.extractSearchParamNumber(theEntity, theResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
|
||||||
|
return mySearchParamExtractor.extractSearchParamQuantity(theEntity, theResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
|
||||||
|
return mySearchParamExtractor.extractSearchParamStrings(theEntity, theResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
|
||||||
|
return mySearchParamExtractor.extractSearchParamTokens(theEntity, theResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
|
||||||
|
return mySearchParamExtractor.extractSearchParamUri(theEntity, theResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void extractCompositeStringUniques(ResourceTable theEntity, ResourceIndexedSearchParams theParams) {
|
||||||
|
|
||||||
|
List<JpaRuntimeSearchParam> uniqueSearchParams = mySearchParamRegistry.getActiveUniqueSearchParams(theEntity.getResourceType());
|
||||||
|
|
||||||
|
for (JpaRuntimeSearchParam next : uniqueSearchParams) {
|
||||||
|
|
||||||
|
List<List<String>> partsChoices = new ArrayList<>();
|
||||||
|
|
||||||
|
for (RuntimeSearchParam nextCompositeOf : next.getCompositeOf()) {
|
||||||
|
Collection<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null;
|
||||||
|
Collection<ResourceLink> linksForCompositePart = null;
|
||||||
|
Collection<String> linksForCompositePartWantPaths = null;
|
||||||
|
switch (nextCompositeOf.getParamType()) {
|
||||||
|
case NUMBER:
|
||||||
|
paramsListForCompositePart = theParams.numberParams;
|
||||||
|
break;
|
||||||
|
case DATE:
|
||||||
|
paramsListForCompositePart = theParams.dateParams;
|
||||||
|
break;
|
||||||
|
case STRING:
|
||||||
|
paramsListForCompositePart = theParams.stringParams;
|
||||||
|
break;
|
||||||
|
case TOKEN:
|
||||||
|
paramsListForCompositePart = theParams.tokenParams;
|
||||||
|
break;
|
||||||
|
case REFERENCE:
|
||||||
|
linksForCompositePart = theParams.links;
|
||||||
|
linksForCompositePartWantPaths = new HashSet<>();
|
||||||
|
linksForCompositePartWantPaths.addAll(nextCompositeOf.getPathsSplit());
|
||||||
|
break;
|
||||||
|
case QUANTITY:
|
||||||
|
paramsListForCompositePart = theParams.quantityParams;
|
||||||
|
break;
|
||||||
|
case URI:
|
||||||
|
paramsListForCompositePart = theParams.uriParams;
|
||||||
|
break;
|
||||||
|
case COMPOSITE:
|
||||||
|
case HAS:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<String> nextChoicesList = new ArrayList<>();
|
||||||
|
partsChoices.add(nextChoicesList);
|
||||||
|
|
||||||
|
String key = UrlUtil.escapeUrlParam(nextCompositeOf.getName());
|
||||||
|
if (paramsListForCompositePart != null) {
|
||||||
|
for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) {
|
||||||
|
if (nextParam.getParamName().equals(nextCompositeOf.getName())) {
|
||||||
|
IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType();
|
||||||
|
String value = nextParamAsClientParam.getValueAsQueryToken(myContext);
|
||||||
|
if (isNotBlank(value)) {
|
||||||
|
value = UrlUtil.escapeUrlParam(value);
|
||||||
|
nextChoicesList.add(key + "=" + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (linksForCompositePart != null) {
|
||||||
|
for (ResourceLink nextLink : linksForCompositePart) {
|
||||||
|
if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) {
|
||||||
|
String value = nextLink.getTargetResource().getIdDt().toUnqualifiedVersionless().getValue();
|
||||||
|
if (isNotBlank(value)) {
|
||||||
|
value = UrlUtil.escapeUrlParam(value);
|
||||||
|
nextChoicesList.add(key + "=" + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> queryStringsToPopulate = theParams.extractCompositeStringUniquesValueChains(theEntity.getResourceType(), partsChoices);
|
||||||
|
|
||||||
|
for (String nextQueryString : queryStringsToPopulate) {
|
||||||
|
if (isNotBlank(nextQueryString)) {
|
||||||
|
theParams.compositeStringUniques.add(new ResourceIndexedCompositeStringUnique(theEntity, nextQueryString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns a set containing all of the parameter names that
|
||||||
|
* were found to have a value
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, boolean lookUpReferencesInDatabase) {
|
||||||
|
String resourceType = theEntity.getResourceType();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For now we don't try to load any of the links in a bundle if it's the actual bundle we're storing..
|
||||||
|
*/
|
||||||
|
if (theResource instanceof IBaseBundle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, RuntimeSearchParam> searchParams = mySearchParamRegistry.getActiveSearchParams(toResourceName(theResource.getClass()));
|
||||||
|
for (RuntimeSearchParam nextSpDef : searchParams.values()) {
|
||||||
|
|
||||||
|
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String nextPathsUnsplit = nextSpDef.getPath();
|
||||||
|
if (isBlank(nextPathsUnsplit)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean multiType = false;
|
||||||
|
if (nextPathsUnsplit.endsWith("[x]")) {
|
||||||
|
multiType = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PathAndRef> refs = mySearchParamExtractor.extractResourceLinks(theResource, nextSpDef);
|
||||||
|
for (PathAndRef nextPathAndRef : refs) {
|
||||||
|
Object nextObject = nextPathAndRef.getRef();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A search parameter on an extension field that contains
|
||||||
|
* references should index those references
|
||||||
|
*/
|
||||||
|
if (nextObject instanceof IBaseExtension<?, ?>) {
|
||||||
|
nextObject = ((IBaseExtension<?, ?>) nextObject).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextObject instanceof CanonicalType) {
|
||||||
|
nextObject = new Reference(((CanonicalType) nextObject).getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
IIdType nextId;
|
||||||
|
if (nextObject instanceof IBaseReference) {
|
||||||
|
IBaseReference nextValue = (IBaseReference) nextObject;
|
||||||
|
if (nextValue.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
nextId = nextValue.getReferenceElement();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This can only really happen if the DAO is being called
|
||||||
|
* programatically with a Bundle (not through the FHIR REST API)
|
||||||
|
* but Smile does this
|
||||||
|
*/
|
||||||
|
if (nextId.isEmpty() && nextValue.getResource() != null) {
|
||||||
|
nextId = nextValue.getResource().getIdElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextId.isEmpty() || nextId.getValue().startsWith("#")) {
|
||||||
|
// This is a blank or contained resource reference
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (nextObject instanceof IBaseResource) {
|
||||||
|
nextId = ((IBaseResource) nextObject).getIdElement();
|
||||||
|
if (nextId == null || nextId.hasIdPart() == false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (myContext.getElementDefinition((Class<? extends IBase>) nextObject.getClass()).getName().equals("uri")) {
|
||||||
|
continue;
|
||||||
|
} else if (resourceType.equals("Consent") && nextPathAndRef.getPath().equals("Consent.source")) {
|
||||||
|
// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
if (!multiType) {
|
||||||
|
if (nextSpDef.getName().equals("sourceuri")) {
|
||||||
|
continue; // TODO: disable this eventually - ConceptMap:sourceuri is of type reference but points to a URI
|
||||||
|
}
|
||||||
|
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
theParams.populatedResourceLinkParameters.add(nextSpDef.getName());
|
||||||
|
|
||||||
|
if (LogicalReferenceHelper.isLogicalReference(myDaoConfig, nextId)) {
|
||||||
|
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
|
||||||
|
if (theParams.links.add(resourceLink)) {
|
||||||
|
ourLog.debug("Indexing remote resource reference URL: {}", nextId);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String baseUrl = nextId.getBaseUrl();
|
||||||
|
String typeString = nextId.getResourceType();
|
||||||
|
if (isBlank(typeString)) {
|
||||||
|
throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource type - " + nextId.getValue());
|
||||||
|
}
|
||||||
|
RuntimeResourceDefinition resourceDefinition;
|
||||||
|
try {
|
||||||
|
resourceDefinition = myContext.getResourceDefinition(typeString);
|
||||||
|
} catch (DataFormatException e) {
|
||||||
|
throw new InvalidRequestException(
|
||||||
|
"Invalid resource reference found at path[" + nextPathsUnsplit + "] - Resource type is unknown or not supported on this server - " + nextId.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNotBlank(baseUrl)) {
|
||||||
|
if (!myDaoConfig.getTreatBaseUrlsAsLocal().contains(baseUrl) && !myDaoConfig.isAllowExternalReferences()) {
|
||||||
|
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "externalReferenceNotAllowed", nextId.getValue());
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
} else {
|
||||||
|
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
|
||||||
|
if (theParams.links.add(resourceLink)) {
|
||||||
|
ourLog.debug("Indexing remote resource reference URL: {}", nextId);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
|
||||||
|
String id = nextId.getIdPart();
|
||||||
|
if (StringUtils.isBlank(id)) {
|
||||||
|
throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource ID - " + nextId.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
myDaoRegistry.getDaoOrThrowException(type);
|
||||||
|
ResourceTable target;
|
||||||
|
if (lookUpReferencesInDatabase) {
|
||||||
|
Long valueOf;
|
||||||
|
try {
|
||||||
|
valueOf = myIdHelperService.translateForcedIdToPid(typeString, id);
|
||||||
|
} catch (ResourceNotFoundException e) {
|
||||||
|
if (myDaoConfig.isEnforceReferentialIntegrityOnWrite() == false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(type);
|
||||||
|
String resName = missingResourceDef.getName();
|
||||||
|
|
||||||
|
if (myDaoConfig.isAutoCreatePlaceholderReferenceTargets()) {
|
||||||
|
IBaseResource newResource = missingResourceDef.newInstance();
|
||||||
|
newResource.setId(resName + "/" + id);
|
||||||
|
IFhirResourceDao<IBaseResource> placeholderResourceDao = (IFhirResourceDao<IBaseResource>) myDaoRegistry.getResourceDao(newResource.getClass());
|
||||||
|
ourLog.debug("Automatically creating empty placeholder resource: {}", newResource.getIdElement().getValue());
|
||||||
|
valueOf = placeholderResourceDao.update(newResource).getEntity().getId();
|
||||||
|
} else {
|
||||||
|
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target = myEntityManager.find(ResourceTable.class, valueOf);
|
||||||
|
RuntimeResourceDefinition targetResourceDef = myContext.getResourceDefinition(type);
|
||||||
|
if (target == null) {
|
||||||
|
String resName = targetResourceDef.getName();
|
||||||
|
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!typeString.equals(target.getResourceType())) {
|
||||||
|
throw new UnprocessableEntityException(
|
||||||
|
"Resource contains reference to " + nextId.getValue() + " but resource with ID " + nextId.getIdPart() + " is actually of type " + target.getResourceType());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.getDeleted() != null) {
|
||||||
|
String resName = targetResourceDef.getName();
|
||||||
|
throw new InvalidRequestException("Resource " + resName + "/" + id + " is deleted, specified in path: " + nextPathsUnsplit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextSpDef.getTargets() != null && !nextSpDef.getTargets().contains(typeString)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target = new ResourceTable();
|
||||||
|
target.setResourceType(typeString);
|
||||||
|
if (nextId.isIdPartValidLong()) {
|
||||||
|
target.setId(nextId.getIdPartAsLong());
|
||||||
|
} else {
|
||||||
|
ForcedId forcedId = new ForcedId();
|
||||||
|
forcedId.setForcedId(id);
|
||||||
|
target.setForcedId(forcedId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, target, theUpdateTime);
|
||||||
|
theParams.links.add(resourceLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
theEntity.setHasLinks(theParams.links.size() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toResourceName(Class<? extends IBaseResource> theResourceType) {
|
||||||
|
return myContext.getResourceDefinition(theResourceType).getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeCommon(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) {
|
||||||
|
theParams.calculateHashes(theParams.stringParams);
|
||||||
|
for (ResourceIndexedSearchParamString next : removeCommon(existingParams.stringParams, theParams.stringParams)) {
|
||||||
|
next.setDaoConfig(myDaoConfig);
|
||||||
|
myEntityManager.remove(next);
|
||||||
|
theEntity.getParamsString().remove(next);
|
||||||
|
}
|
||||||
|
for (ResourceIndexedSearchParamString next : removeCommon(theParams.stringParams, existingParams.stringParams)) {
|
||||||
|
myEntityManager.persist(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
theParams.calculateHashes(theParams.tokenParams);
|
||||||
|
for (ResourceIndexedSearchParamToken next : removeCommon(existingParams.tokenParams, theParams.tokenParams)) {
|
||||||
|
myEntityManager.remove(next);
|
||||||
|
theEntity.getParamsToken().remove(next);
|
||||||
|
}
|
||||||
|
for (ResourceIndexedSearchParamToken next : removeCommon(theParams.tokenParams, existingParams.tokenParams)) {
|
||||||
|
myEntityManager.persist(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
theParams.calculateHashes(theParams.numberParams);
|
||||||
|
for (ResourceIndexedSearchParamNumber next : removeCommon(existingParams.numberParams, theParams.numberParams)) {
|
||||||
|
myEntityManager.remove(next);
|
||||||
|
theEntity.getParamsNumber().remove(next);
|
||||||
|
}
|
||||||
|
for (ResourceIndexedSearchParamNumber next : removeCommon(theParams.numberParams, existingParams.numberParams)) {
|
||||||
|
myEntityManager.persist(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
theParams.calculateHashes(theParams.quantityParams);
|
||||||
|
for (ResourceIndexedSearchParamQuantity next : removeCommon(existingParams.quantityParams, theParams.quantityParams)) {
|
||||||
|
myEntityManager.remove(next);
|
||||||
|
theEntity.getParamsQuantity().remove(next);
|
||||||
|
}
|
||||||
|
for (ResourceIndexedSearchParamQuantity next : removeCommon(theParams.quantityParams, existingParams.quantityParams)) {
|
||||||
|
myEntityManager.persist(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store date SP's
|
||||||
|
theParams.calculateHashes(theParams.dateParams);
|
||||||
|
for (ResourceIndexedSearchParamDate next : removeCommon(existingParams.dateParams, theParams.dateParams)) {
|
||||||
|
myEntityManager.remove(next);
|
||||||
|
theEntity.getParamsDate().remove(next);
|
||||||
|
}
|
||||||
|
for (ResourceIndexedSearchParamDate next : removeCommon(theParams.dateParams, existingParams.dateParams)) {
|
||||||
|
myEntityManager.persist(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store URI SP's
|
||||||
|
theParams.calculateHashes(theParams.uriParams);
|
||||||
|
for (ResourceIndexedSearchParamUri next : removeCommon(existingParams.uriParams, theParams.uriParams)) {
|
||||||
|
myEntityManager.remove(next);
|
||||||
|
theEntity.getParamsUri().remove(next);
|
||||||
|
}
|
||||||
|
for (ResourceIndexedSearchParamUri next : removeCommon(theParams.uriParams, existingParams.uriParams)) {
|
||||||
|
myEntityManager.persist(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store Coords SP's
|
||||||
|
theParams.calculateHashes(theParams.coordsParams);
|
||||||
|
for (ResourceIndexedSearchParamCoords next : removeCommon(existingParams.coordsParams, theParams.coordsParams)) {
|
||||||
|
myEntityManager.remove(next);
|
||||||
|
theEntity.getParamsCoords().remove(next);
|
||||||
|
}
|
||||||
|
for (ResourceIndexedSearchParamCoords next : removeCommon(theParams.coordsParams, existingParams.coordsParams)) {
|
||||||
|
myEntityManager.persist(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store resource links
|
||||||
|
for (ResourceLink next : removeCommon(existingParams.links, theParams.links)) {
|
||||||
|
myEntityManager.remove(next);
|
||||||
|
theEntity.getResourceLinks().remove(next);
|
||||||
|
}
|
||||||
|
for (ResourceLink next : removeCommon(theParams.links, existingParams.links)) {
|
||||||
|
myEntityManager.persist(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure links are indexed
|
||||||
|
theEntity.setResourceLinks(theParams.links);
|
||||||
|
|
||||||
|
// Store composite string uniques
|
||||||
|
if (myDaoConfig.isUniqueIndexesEnabled()) {
|
||||||
|
for (ResourceIndexedCompositeStringUnique next : removeCommon(existingParams.compositeStringUniques, theParams.compositeStringUniques)) {
|
||||||
|
ourLog.debug("Removing unique index: {}", next);
|
||||||
|
myEntityManager.remove(next);
|
||||||
|
theEntity.getParamsCompositeStringUnique().remove(next);
|
||||||
|
}
|
||||||
|
for (ResourceIndexedCompositeStringUnique next : removeCommon(theParams.compositeStringUniques, existingParams.compositeStringUniques)) {
|
||||||
|
if (myDaoConfig.isUniqueIndexesCheckedBeforeSave()) {
|
||||||
|
ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString());
|
||||||
|
if (existing != null) {
|
||||||
|
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "uniqueIndexConflictFailure", theEntity.getResourceType(), next.getIndexString(), existing.getResource().getIdDt().toUnqualifiedVersionless().getValue());
|
||||||
|
throw new PreconditionFailedException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ourLog.debug("Persisting unique index: {}", next);
|
||||||
|
myEntityManager.persist(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> Collection<T> removeCommon(Collection<T> theInput, Collection<T> theToRemove) {
|
||||||
|
assert theInput != theToRemove;
|
||||||
|
|
||||||
|
if (theInput.isEmpty()) {
|
||||||
|
return theInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<T> retVal = new ArrayList<>(theInput);
|
||||||
|
retVal.removeAll(theToRemove);
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ import ca.uhn.fhir.jpa.entity.TermCodeSystem;
|
||||||
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
|
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
|
||||||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
import ca.uhn.fhir.jpa.entity.TermConcept;
|
||||||
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
|
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
|
||||||
import ca.uhn.fhir.jpa.util.LogicUtil;
|
import ca.uhn.fhir.jpa.util.LogicUtil;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
|
@ -64,8 +63,6 @@ public class FhirResourceDaoCodeSystemR4 extends FhirResourceDaoR4<CodeSystem> i
|
||||||
@Autowired
|
@Autowired
|
||||||
private ITermCodeSystemDao myCsDao;
|
private ITermCodeSystemDao myCsDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IHapiTerminologySvc myTerminologySvc;
|
|
||||||
@Autowired
|
|
||||||
private ValidationSupportChain myValidationSupport;
|
private ValidationSupportChain myValidationSupport;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -156,4 +156,7 @@ public abstract class BaseResourceIndexedSearchParam implements Serializable {
|
||||||
return hashCode.asLong();
|
return hashCode.asLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean matches(IQueryParameterType theParam) {
|
||||||
|
throw new UnsupportedOperationException("No parameter matcher for "+theParam);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
import ca.uhn.fhir.rest.param.DateParam;
|
import ca.uhn.fhir.rest.param.DateParam;
|
||||||
|
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
@ -184,4 +185,31 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
|
||||||
b.append("valueHigh", new InstantDt(getValueHigh()));
|
b.append("valueHigh", new InstantDt(getValueHigh()));
|
||||||
return b.build();
|
return b.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(IQueryParameterType theParam) {
|
||||||
|
if (!(theParam instanceof DateParam)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DateParam date = (DateParam) theParam;
|
||||||
|
DateRangeParam range = new DateRangeParam(date);
|
||||||
|
Date lowerBound = range.getLowerBoundAsInstant();
|
||||||
|
Date upperBound = range.getUpperBoundAsInstant();
|
||||||
|
|
||||||
|
if (lowerBound == null && upperBound == null) {
|
||||||
|
// should never happen
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean result = true;
|
||||||
|
if (lowerBound != null) {
|
||||||
|
result &= (myValueLow.after(lowerBound) || myValueLow.equals(lowerBound));
|
||||||
|
result &= (myValueHigh.after(lowerBound) || myValueHigh.equals(lowerBound));
|
||||||
|
}
|
||||||
|
if (upperBound != null) {
|
||||||
|
result &= (myValueLow.before(upperBound) || myValueLow.equals(upperBound));
|
||||||
|
result &= (myValueHigh.before(upperBound) || myValueHigh.equals(upperBound));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,4 +149,13 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
|
||||||
b.append("value", getValue());
|
b.append("value", getValue());
|
||||||
return b.build();
|
return b.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(IQueryParameterType theParam) {
|
||||||
|
if (!(theParam instanceof NumberParam)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
NumberParam number = (NumberParam)theParam;
|
||||||
|
return getValue().equals(number.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,4 +235,37 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
|
||||||
return hash(theResourceType, theParamName, theUnits);
|
return hash(theResourceType, theParamName, theUnits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(IQueryParameterType theParam) {
|
||||||
|
if (!(theParam instanceof QuantityParam)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QuantityParam quantity = (QuantityParam)theParam;
|
||||||
|
boolean retval = false;
|
||||||
|
|
||||||
|
// Only match on system if it wasn't specified
|
||||||
|
if (quantity.getSystem() == null && quantity.getUnits() == null) {
|
||||||
|
if (getValue().equals(quantity.getValue())) {
|
||||||
|
retval = true;
|
||||||
|
}
|
||||||
|
} else if (quantity.getSystem() == null) {
|
||||||
|
if (getUnits().equalsIgnoreCase(quantity.getUnits()) &&
|
||||||
|
getValue().equals(quantity.getValue())) {
|
||||||
|
retval = true;
|
||||||
|
}
|
||||||
|
} else if (quantity.getUnits() == null) {
|
||||||
|
if (getSystem().equalsIgnoreCase(quantity.getSystem()) &&
|
||||||
|
getValue().equals(quantity.getValue())) {
|
||||||
|
retval = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (getSystem().equalsIgnoreCase(quantity.getSystem()) &&
|
||||||
|
getUnits().equalsIgnoreCase(quantity.getUnits()) &&
|
||||||
|
getValue().equals(quantity.getValue())) {
|
||||||
|
retval = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.entity;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
|
@ -294,4 +295,13 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
|
||||||
return hash(theResourceType, theParamName, left(theValueNormalized, hashPrefixLength));
|
return hash(theResourceType, theParamName, left(theValueNormalized, hashPrefixLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(IQueryParameterType theParam) {
|
||||||
|
if (!(theParam instanceof StringParam)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
StringParam string = (StringParam)theParam;
|
||||||
|
String normalizedString = BaseHapiFhirDao.normalizeString(string.getValue());
|
||||||
|
return getValueNormalized().startsWith(normalizedString);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -251,4 +251,29 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
|
||||||
public static long calculateHashValue(String theResourceType, String theParamName, String theValue) {
|
public static long calculateHashValue(String theResourceType, String theParamName, String theValue) {
|
||||||
return hash(theResourceType, theParamName, trim(theValue));
|
return hash(theResourceType, theParamName, trim(theValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(IQueryParameterType theParam) {
|
||||||
|
if (!(theParam instanceof TokenParam)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TokenParam token = (TokenParam)theParam;
|
||||||
|
boolean retval = false;
|
||||||
|
// Only match on system if it wasn't specified
|
||||||
|
if (token.getSystem() == null || token.getSystem().isEmpty()) {
|
||||||
|
if (getValue().equalsIgnoreCase(token.getValue())) {
|
||||||
|
retval = true;
|
||||||
|
}
|
||||||
|
} else if (token.getValue() == null || token.getValue().isEmpty()) {
|
||||||
|
if (token.getSystem().equalsIgnoreCase(getSystem())) {
|
||||||
|
retval = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (token.getSystem().equalsIgnoreCase(getSystem()) &&
|
||||||
|
getValue().equalsIgnoreCase(token.getValue())) {
|
||||||
|
retval = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,4 +179,13 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
|
||||||
return hash(theResourceType, theParamName, theUri);
|
return hash(theResourceType, theParamName, theUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(IQueryParameterType theParam) {
|
||||||
|
if (!(theParam instanceof UriParam)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
UriParam uri = (UriParam)theParam;
|
||||||
|
return getUri().equalsIgnoreCase(uri.getValueNotNull());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class JpaStorageServices extends BaseHapiFhirDao<IBaseResource> implement
|
||||||
|
|
||||||
for (Argument nextArgument : theSearchParams) {
|
for (Argument nextArgument : theSearchParams) {
|
||||||
|
|
||||||
RuntimeSearchParam searchParam = getSearchParamByName(typeDef, nextArgument.getName());
|
RuntimeSearchParam searchParam = mySearchParamRegistry.getSearchParamByName(typeDef, nextArgument.getName());
|
||||||
|
|
||||||
for (Value nextValue : nextArgument.getValues()) {
|
for (Value nextValue : nextArgument.getValues()) {
|
||||||
String value = nextValue.getValue();
|
String value = nextValue.getValue();
|
||||||
|
|
|
@ -22,12 +22,12 @@ package ca.uhn.fhir.jpa.provider;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
|
||||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||||
import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
|
import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
|
||||||
|
import ca.uhn.fhir.jpa.dao.MatchUrlService;
|
||||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||||
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
||||||
import ca.uhn.fhir.jpa.util.JpaConstants;
|
import ca.uhn.fhir.jpa.util.JpaConstants;
|
||||||
|
@ -86,6 +86,8 @@ public class SubscriptionTriggeringProvider implements IResourceProvider, Applic
|
||||||
private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT;
|
private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT;
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
private ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||||
|
@Autowired
|
||||||
|
private MatchUrlService myMatchUrlService;
|
||||||
private ApplicationContext myAppCtx;
|
private ApplicationContext myAppCtx;
|
||||||
private ExecutorService myExecutorService;
|
private ExecutorService myExecutorService;
|
||||||
|
|
||||||
|
@ -283,7 +285,7 @@ public class SubscriptionTriggeringProvider implements IResourceProvider, Applic
|
||||||
String resourceType = resourceDef.getName();
|
String resourceType = resourceDef.getName();
|
||||||
|
|
||||||
IFhirResourceDao<?> callingDao = myDaoRegistry.getResourceDao(resourceType);
|
IFhirResourceDao<?> callingDao = myDaoRegistry.getResourceDao(resourceType);
|
||||||
SearchParameterMap params = BaseHapiFhirDao.translateMatchUrl(callingDao, myFhirContext, queryPart, resourceDef);
|
SearchParameterMap params = myMatchUrlService.translateMatchUrl(queryPart, resourceDef);
|
||||||
|
|
||||||
ourLog.info("Triggering job[{}] is starting a search for {}", theJobDetails.getJobId(), nextSearchUrl);
|
ourLog.info("Triggering job[{}] is starting a search for {}", theJobDetails.getJobId(), nextSearchUrl);
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.util.*;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
|
||||||
import org.hl7.fhir.dstu3.model.*;
|
import org.hl7.fhir.dstu3.model.*;
|
||||||
import org.hl7.fhir.dstu3.model.CapabilityStatement.*;
|
import org.hl7.fhir.dstu3.model.CapabilityStatement.*;
|
||||||
import org.hl7.fhir.dstu3.model.Enumerations.SearchParamType;
|
import org.hl7.fhir.dstu3.model.Enumerations.SearchParamType;
|
||||||
|
@ -41,6 +42,7 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
|
||||||
|
|
||||||
private volatile CapabilityStatement myCachedValue;
|
private volatile CapabilityStatement myCachedValue;
|
||||||
private DaoConfig myDaoConfig;
|
private DaoConfig myDaoConfig;
|
||||||
|
private ISearchParamRegistry mySearchParamRegistry;
|
||||||
private String myImplementationDescription;
|
private String myImplementationDescription;
|
||||||
private boolean myIncludeResourceCounts;
|
private boolean myIncludeResourceCounts;
|
||||||
private RestfulServer myRestfulServer;
|
private RestfulServer myRestfulServer;
|
||||||
|
@ -64,6 +66,7 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
|
||||||
myRestfulServer = theRestfulServer;
|
myRestfulServer = theRestfulServer;
|
||||||
mySystemDao = theSystemDao;
|
mySystemDao = theSystemDao;
|
||||||
myDaoConfig = theDaoConfig;
|
myDaoConfig = theDaoConfig;
|
||||||
|
mySearchParamRegistry = theSystemDao.getSearchParamRegistry();
|
||||||
super.setCache(false);
|
super.setCache(false);
|
||||||
setIncludeResourceCounts(true);
|
setIncludeResourceCounts(true);
|
||||||
}
|
}
|
||||||
|
@ -99,7 +102,7 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
|
||||||
nextResource.getSearchParam().clear();
|
nextResource.getSearchParam().clear();
|
||||||
String resourceName = nextResource.getType();
|
String resourceName = nextResource.getType();
|
||||||
RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName);
|
RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName);
|
||||||
Collection<RuntimeSearchParam> searchParams = mySystemDao.getSearchParamsByResourceType(resourceDef);
|
Collection<RuntimeSearchParam> searchParams = mySearchParamRegistry.getSearchParamsByResourceType(resourceDef);
|
||||||
for (RuntimeSearchParam runtimeSp : searchParams) {
|
for (RuntimeSearchParam runtimeSp : searchParams) {
|
||||||
CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam();
|
CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam();
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.util.*;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.*;
|
||||||
import org.hl7.fhir.r4.model.CapabilityStatement.*;
|
import org.hl7.fhir.r4.model.CapabilityStatement.*;
|
||||||
import org.hl7.fhir.r4.model.Enumerations.SearchParamType;
|
import org.hl7.fhir.r4.model.Enumerations.SearchParamType;
|
||||||
|
@ -41,6 +42,7 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S
|
||||||
|
|
||||||
private volatile CapabilityStatement myCachedValue;
|
private volatile CapabilityStatement myCachedValue;
|
||||||
private DaoConfig myDaoConfig;
|
private DaoConfig myDaoConfig;
|
||||||
|
private ISearchParamRegistry mySearchParamRegistry;
|
||||||
private String myImplementationDescription;
|
private String myImplementationDescription;
|
||||||
private boolean myIncludeResourceCounts;
|
private boolean myIncludeResourceCounts;
|
||||||
private RestfulServer myRestfulServer;
|
private RestfulServer myRestfulServer;
|
||||||
|
@ -64,6 +66,7 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S
|
||||||
myRestfulServer = theRestfulServer;
|
myRestfulServer = theRestfulServer;
|
||||||
mySystemDao = theSystemDao;
|
mySystemDao = theSystemDao;
|
||||||
myDaoConfig = theDaoConfig;
|
myDaoConfig = theDaoConfig;
|
||||||
|
mySearchParamRegistry = theSystemDao.getSearchParamRegistry();
|
||||||
super.setCache(false);
|
super.setCache(false);
|
||||||
setIncludeResourceCounts(true);
|
setIncludeResourceCounts(true);
|
||||||
}
|
}
|
||||||
|
@ -99,7 +102,7 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S
|
||||||
nextResource.getSearchParam().clear();
|
nextResource.getSearchParam().clear();
|
||||||
String resourceName = nextResource.getType();
|
String resourceName = nextResource.getType();
|
||||||
RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName);
|
RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName);
|
||||||
Collection<RuntimeSearchParam> searchParams = mySystemDao.getSearchParamsByResourceType(resourceDef);
|
Collection<RuntimeSearchParam> searchParams = mySearchParamRegistry.getSearchParamsByResourceType(resourceDef);
|
||||||
for (RuntimeSearchParam runtimeSp : searchParams) {
|
for (RuntimeSearchParam runtimeSp : searchParams) {
|
||||||
CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam();
|
CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam();
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,9 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.server.BasePagingProvider;
|
import ca.uhn.fhir.rest.server.BasePagingProvider;
|
||||||
import ca.uhn.fhir.rest.server.IPagingProvider;
|
import ca.uhn.fhir.rest.server.IPagingProvider;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
public class DatabaseBackedPagingProvider extends BasePagingProvider implements IPagingProvider {
|
public class DatabaseBackedPagingProvider extends BasePagingProvider implements IPagingProvider {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
|
@ -276,8 +276,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
protected List<IBaseResource> toResourceList(ISearchBuilder sb, List<Long> pidsSubList) {
|
protected List<IBaseResource> toResourceList(ISearchBuilder sb, List<Long> pidsSubList) {
|
||||||
Set<Long> includedPids = new HashSet<>();
|
Set<Long> includedPids = new HashSet<>();
|
||||||
if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) {
|
if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) {
|
||||||
includedPids.addAll(sb.loadIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated(), myUuid));
|
includedPids.addAll(sb.loadIncludes(myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated(), myUuid));
|
||||||
includedPids.addAll(sb.loadIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated(), myUuid));
|
includedPids.addAll(sb.loadIncludes(myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated(), myUuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the query and make sure we return distinct results
|
// Execute the query and make sure we return distinct results
|
||||||
|
|
|
@ -47,11 +47,15 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
import org.apache.commons.lang3.time.DateUtils;
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.*;
|
import org.springframework.data.domain.AbstractPageRequest;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.orm.jpa.JpaDialect;
|
import org.springframework.orm.jpa.JpaDialect;
|
||||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||||
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
|
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
|
||||||
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
|
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.TransactionDefinition;
|
import org.springframework.transaction.TransactionDefinition;
|
||||||
import org.springframework.transaction.TransactionStatus;
|
import org.springframework.transaction.TransactionStatus;
|
||||||
|
@ -68,6 +72,7 @@ import java.util.concurrent.*;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||||
|
|
||||||
|
@Component("mySearchCoordinatorSvc")
|
||||||
public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
public static final int DEFAULT_SYNC_SIZE = 250;
|
public static final int DEFAULT_SYNC_SIZE = 250;
|
||||||
|
|
||||||
|
@ -313,8 +318,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
* individually for pages as we return them to clients
|
* individually for pages as we return them to clients
|
||||||
*/
|
*/
|
||||||
final Set<Long> includedPids = new HashSet<>();
|
final Set<Long> includedPids = new HashSet<>();
|
||||||
includedPids.addAll(sb.loadIncludes(theCallingDao, myContext, myEntityManager, pids, theParams.getRevIncludes(), true, theParams.getLastUpdated(), "(synchronous)"));
|
includedPids.addAll(sb.loadIncludes(myContext, myEntityManager, pids, theParams.getRevIncludes(), true, theParams.getLastUpdated(), "(synchronous)"));
|
||||||
includedPids.addAll(sb.loadIncludes(theCallingDao, myContext, myEntityManager, pids, theParams.getIncludes(), false, theParams.getLastUpdated(), "(synchronous)"));
|
includedPids.addAll(sb.loadIncludes(myContext, myEntityManager, pids, theParams.getIncludes(), false, theParams.getLastUpdated(), "(synchronous)"));
|
||||||
|
|
||||||
List<IBaseResource> resources = new ArrayList<>();
|
List<IBaseResource> resources = new ArrayList<>();
|
||||||
sb.loadResourcesByPid(pids, resources, includedPids, false, myEntityManager, myContext, theCallingDao);
|
sb.loadResourcesByPid(pids, resources, includedPids, false, myEntityManager, myContext, theCallingDao);
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
import org.springframework.data.domain.Slice;
|
import org.springframework.data.domain.Slice;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
@ -43,6 +44,7 @@ import java.util.Date;
|
||||||
/**
|
/**
|
||||||
* Deletes old searches
|
* Deletes old searches
|
||||||
*/
|
*/
|
||||||
|
@Service
|
||||||
public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
|
public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
|
||||||
public static final long DEFAULT_CUTOFF_SLACK = 10 * DateUtils.MILLIS_PER_SECOND;
|
public static final long DEFAULT_CUTOFF_SLACK = 10 * DateUtils.MILLIS_PER_SECOND;
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StaleSearchDeletingSvcImpl.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StaleSearchDeletingSvcImpl.class);
|
||||||
|
|
|
@ -458,7 +458,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
|
||||||
* not get this error, so we'll let the other one fail and try
|
* not get this error, so we'll let the other one fail and try
|
||||||
* again later.
|
* again later.
|
||||||
*/
|
*/
|
||||||
ourLog.info("Failed to reindex {} because of a version conflict. Leaving in unindexed state: {}", e.getMessage());
|
ourLog.info("Failed to reindex because of a version conflict. Leaving in unindexed state: {}", e.getMessage());
|
||||||
reindexFailure = null;
|
reindexFailure = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,10 +23,15 @@ package ca.uhn.fhir.jpa.search.warm;
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.jpa.dao.*;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||||
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.jpa.dao.MatchUrlService;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -34,6 +39,7 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
public class CacheWarmingSvcImpl implements ICacheWarmingSvc {
|
public class CacheWarmingSvcImpl implements ICacheWarmingSvc {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -43,6 +49,8 @@ public class CacheWarmingSvcImpl implements ICacheWarmingSvc {
|
||||||
private FhirContext myCtx;
|
private FhirContext myCtx;
|
||||||
@Autowired
|
@Autowired
|
||||||
private DaoRegistry myDaoRegistry;
|
private DaoRegistry myDaoRegistry;
|
||||||
|
@Autowired
|
||||||
|
private MatchUrlService myMatchUrlService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Scheduled(fixedDelay = 1000)
|
@Scheduled(fixedDelay = 1000)
|
||||||
|
@ -72,7 +80,7 @@ public class CacheWarmingSvcImpl implements ICacheWarmingSvc {
|
||||||
RuntimeResourceDefinition resourceDef = parseUrlResourceType(myCtx, nextUrl);
|
RuntimeResourceDefinition resourceDef = parseUrlResourceType(myCtx, nextUrl);
|
||||||
IFhirResourceDao<?> callingDao = myDaoRegistry.getResourceDao(resourceDef.getName());
|
IFhirResourceDao<?> callingDao = myDaoRegistry.getResourceDao(resourceDef.getName());
|
||||||
String queryPart = parseWarmUrlParamPart(nextUrl);
|
String queryPart = parseWarmUrlParamPart(nextUrl);
|
||||||
SearchParameterMap responseCriteriaUrl = BaseHapiFhirDao.translateMatchUrl(callingDao, myCtx, queryPart, resourceDef);
|
SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(queryPart, resourceDef);
|
||||||
|
|
||||||
callingDao.search(responseCriteriaUrl);
|
callingDao.search(responseCriteriaUrl);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,12 @@ import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao;
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchParamPresent;
|
import ca.uhn.fhir.jpa.entity.SearchParamPresent;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
@Service
|
||||||
public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
|
public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
|
@ -20,21 +20,19 @@ package ca.uhn.fhir.jpa.subscription;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
|
||||||
import org.hl7.fhir.r4.model.Subscription;
|
import org.hl7.fhir.r4.model.Subscription;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.messaging.Message;
|
import org.springframework.messaging.Message;
|
||||||
import org.springframework.messaging.MessagingException;
|
import org.springframework.messaging.MessagingException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
public abstract class BaseSubscriptionDeliverySubscriber extends BaseSubscriptionSubscriber {
|
public abstract class BaseSubscriptionDeliverySubscriber extends BaseSubscriptionSubscriber {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionDeliverySubscriber.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionDeliverySubscriber.class);
|
||||||
|
|
||||||
public BaseSubscriptionDeliverySubscriber(IFhirResourceDao<?> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
|
public BaseSubscriptionDeliverySubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
|
||||||
super(theSubscriptionDao, theChannelType, theSubscriptionInterceptor);
|
super(theChannelType, theSubscriptionInterceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -59,21 +57,6 @@ public abstract class BaseSubscriptionDeliverySubscriber extends BaseSubscriptio
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the resource
|
|
||||||
IIdType payloadId = msg.getPayloadId(getContext());
|
|
||||||
Class type = getContext().getResourceDefinition(payloadId.getResourceType()).getImplementingClass();
|
|
||||||
IFhirResourceDao dao = getSubscriptionInterceptor().getDao(type);
|
|
||||||
IBaseResource loadedPayload;
|
|
||||||
try {
|
|
||||||
loadedPayload = dao.read(payloadId);
|
|
||||||
} catch (ResourceNotFoundException e) {
|
|
||||||
// This can happen if a last minute failure happens when saving a resource,
|
|
||||||
// eg a constraint causes the transaction to roll back on commit
|
|
||||||
ourLog.warn("Unable to find resource {} - Aborting delivery", payloadId.getValue());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
msg.setPayload(getContext(), loadedPayload);
|
|
||||||
|
|
||||||
handleMessage(msg);
|
handleMessage(msg);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String msg = "Failure handling subscription payload for subscription: " + subscriptionId;
|
String msg = "Failure handling subscription payload for subscription: " + subscriptionId;
|
||||||
|
|
|
@ -1,42 +1,27 @@
|
||||||
package ca.uhn.fhir.jpa.subscription;
|
package ca.uhn.fhir.jpa.subscription;
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2018 University Health Network
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||||
|
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
||||||
import ca.uhn.fhir.jpa.subscription.matcher.ISubscriptionMatcher;
|
import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
|
||||||
|
import ca.uhn.fhir.jpa.dao.MatchUrlService;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.matcher.SubscriptionMatcherCompositeInMemoryDatabase;
|
||||||
import ca.uhn.fhir.jpa.subscription.matcher.SubscriptionMatcherDatabase;
|
import ca.uhn.fhir.jpa.subscription.matcher.SubscriptionMatcherDatabase;
|
||||||
import ca.uhn.fhir.jpa.util.JpaConstants;
|
import ca.uhn.fhir.jpa.util.JpaConstants;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
|
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
|
||||||
import ca.uhn.fhir.util.StopWatch;
|
import ca.uhn.fhir.util.StopWatch;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
@ -52,6 +37,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.Subscription;
|
import org.hl7.fhir.r4.model.Subscription;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.DisposableBean;
|
import org.springframework.beans.factory.DisposableBean;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
@ -73,6 +59,26 @@ import javax.annotation.PreDestroy;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2018 University Health Network
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> extends ServerOperationInterceptorAdapter {
|
public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> extends ServerOperationInterceptorAdapter {
|
||||||
|
|
||||||
static final String SUBSCRIPTION_STATUS = "Subscription.status";
|
static final String SUBSCRIPTION_STATUS = "Subscription.status";
|
||||||
|
@ -91,9 +97,7 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
private Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionInterceptor.class);
|
private Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionInterceptor.class);
|
||||||
private ThreadPoolExecutor myDeliveryExecutor;
|
private ThreadPoolExecutor myDeliveryExecutor;
|
||||||
private LinkedBlockingQueue<Runnable> myProcessingExecutorQueue;
|
private LinkedBlockingQueue<Runnable> myProcessingExecutorQueue;
|
||||||
private IFhirResourceDao<?> mySubscriptionDao;
|
|
||||||
@Autowired
|
|
||||||
private List<IFhirResourceDao<?>> myResourceDaos;
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private FhirContext myCtx;
|
private FhirContext myCtx;
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
|
@ -104,7 +108,16 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier(BaseConfig.TASK_EXECUTOR_NAME)
|
@Qualifier(BaseConfig.TASK_EXECUTOR_NAME)
|
||||||
private AsyncTaskExecutor myAsyncTaskExecutor;
|
private AsyncTaskExecutor myAsyncTaskExecutor;
|
||||||
private Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> myResourceTypeToDao;
|
@Autowired
|
||||||
|
private SubscriptionMatcherCompositeInMemoryDatabase mySubscriptionMatcherCompositeInMemoryDatabase;
|
||||||
|
@Autowired
|
||||||
|
private SubscriptionMatcherDatabase mySubscriptionMatcherDatabase;
|
||||||
|
@Autowired
|
||||||
|
private DaoRegistry myDaoRegistry;
|
||||||
|
@Autowired
|
||||||
|
private BeanFactory beanFactory;
|
||||||
|
@Autowired
|
||||||
|
private MatchUrlService myMatchUrlService;
|
||||||
private Semaphore myInitSubscriptionsSemaphore = new Semaphore(1);
|
private Semaphore myInitSubscriptionsSemaphore = new Semaphore(1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -286,26 +299,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
|
|
||||||
public abstract Subscription.SubscriptionChannelType getChannelType();
|
public abstract Subscription.SubscriptionChannelType getChannelType();
|
||||||
|
|
||||||
// TODO KHS move out
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType) {
|
|
||||||
if (myResourceTypeToDao == null) {
|
|
||||||
Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> theResourceTypeToDao = new HashMap<>();
|
|
||||||
for (IFhirResourceDao<?> next : myResourceDaos) {
|
|
||||||
theResourceTypeToDao.put(next.getResourceType(), next);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this instanceof IFhirResourceDao<?>) {
|
|
||||||
IFhirResourceDao<?> thiz = (IFhirResourceDao<?>) this;
|
|
||||||
theResourceTypeToDao.put(thiz.getResourceType(), thiz);
|
|
||||||
}
|
|
||||||
|
|
||||||
myResourceTypeToDao = theResourceTypeToDao;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (IFhirResourceDao<R>) myResourceTypeToDao.get(theType);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MessageChannel getDeliveryChannel(CanonicalSubscription theSubscription) {
|
protected MessageChannel getDeliveryChannel(CanonicalSubscription theSubscription) {
|
||||||
return mySubscribableChannel.get(theSubscription.getIdElement(myCtx).getIdPart());
|
return mySubscribableChannel.get(theSubscription.getIdElement(myCtx).getIdPart());
|
||||||
}
|
}
|
||||||
|
@ -335,9 +328,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
myProcessingChannel = theProcessingChannel;
|
myProcessingChannel = theProcessingChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IFhirResourceDao<?> getSubscriptionDao() {
|
|
||||||
return mySubscriptionDao;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<CanonicalSubscription> getRegisteredSubscriptions() {
|
public List<CanonicalSubscription> getRegisteredSubscriptions() {
|
||||||
return new ArrayList<>(myIdToSubscription.values());
|
return new ArrayList<>(myIdToSubscription.values());
|
||||||
|
@ -378,7 +368,8 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
RequestDetails req = new ServletSubRequestDetails();
|
RequestDetails req = new ServletSubRequestDetails();
|
||||||
req.setSubRequest(true);
|
req.setSubRequest(true);
|
||||||
|
|
||||||
IBundleProvider subscriptionBundleList = getSubscriptionDao().search(map, req);
|
IFhirResourceDao<?> subscriptionDao = myDaoRegistry.getResourceDao("Subscription");
|
||||||
|
IBundleProvider subscriptionBundleList = subscriptionDao.search(map, req);
|
||||||
if (subscriptionBundleList.size() >= MAX_SUBSCRIPTION_RESULTS) {
|
if (subscriptionBundleList.size() >= MAX_SUBSCRIPTION_RESULTS) {
|
||||||
ourLog.error("Currently over " + MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded.");
|
ourLog.error("Currently over " + MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded.");
|
||||||
}
|
}
|
||||||
|
@ -436,8 +427,7 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
|
|
||||||
protected void registerSubscriptionCheckingSubscriber() {
|
protected void registerSubscriptionCheckingSubscriber() {
|
||||||
if (mySubscriptionCheckingSubscriber == null) {
|
if (mySubscriptionCheckingSubscriber == null) {
|
||||||
ISubscriptionMatcher subscriptionMatcher = new SubscriptionMatcherDatabase(getSubscriptionDao(), this);
|
mySubscriptionCheckingSubscriber = beanFactory.getBean(SubscriptionCheckingSubscriber.class, getChannelType(), this, mySubscriptionMatcherCompositeInMemoryDatabase);
|
||||||
mySubscriptionCheckingSubscriber = new SubscriptionCheckingSubscriber(getSubscriptionDao(), getChannelType(), this, subscriptionMatcher );
|
|
||||||
}
|
}
|
||||||
getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber);
|
getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber);
|
||||||
}
|
}
|
||||||
|
@ -501,9 +491,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
myCtx = theCtx;
|
myCtx = theCtx;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResourceDaos(List<IFhirResourceDao<?>> theResourceDaos) {
|
|
||||||
myResourceDaos = theResourceDaos;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void setTxManager(PlatformTransactionManager theTxManager) {
|
public void setTxManager(PlatformTransactionManager theTxManager) {
|
||||||
|
@ -512,15 +499,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void start() {
|
public void start() {
|
||||||
for (IFhirResourceDao<?> next : myResourceDaos) {
|
|
||||||
if (next.getResourceType() != null) {
|
|
||||||
if (myCtx.getResourceDefinition(next.getResourceType()).getName().equals("Subscription")) {
|
|
||||||
mySubscriptionDao = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Validate.notNull(mySubscriptionDao);
|
|
||||||
|
|
||||||
if (myCtx.getVersion().getVersion() == FhirVersionEnum.R4) {
|
if (myCtx.getVersion().getVersion() == FhirVersionEnum.R4) {
|
||||||
Validate.notNull(myEventDefinitionDaoR4);
|
Validate.notNull(myEventDefinitionDaoR4);
|
||||||
}
|
}
|
||||||
|
@ -555,7 +533,8 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mySubscriptionActivatingSubscriber == null) {
|
if (mySubscriptionActivatingSubscriber == null) {
|
||||||
mySubscriptionActivatingSubscriber = new SubscriptionActivatingSubscriber(getSubscriptionDao(), getChannelType(), this, myTxManager, myAsyncTaskExecutor);
|
IFhirResourceDao<?> subscriptionDao = myDaoRegistry.getResourceDao("Subscription");
|
||||||
|
mySubscriptionActivatingSubscriber = new SubscriptionActivatingSubscriber(subscriptionDao, getChannelType(), this, myTxManager, myAsyncTaskExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
registerSubscriptionCheckingSubscriber();
|
registerSubscriptionCheckingSubscriber();
|
||||||
|
@ -620,4 +599,26 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public IFhirResourceDao<?> getSubscriptionDao() {
|
||||||
|
return myDaoRegistry.getResourceDao("Subscription");
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFhirResourceDao getDao(Class type) {
|
||||||
|
return myDaoRegistry.getResourceDao(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceDaos(List<IFhirResourceDao> theResourceDaos) {
|
||||||
|
myDaoRegistry.setResourceDaos(theResourceDaos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateCriteria(final S theResource) {
|
||||||
|
CanonicalSubscription subscription = canonicalize(theResource);
|
||||||
|
String criteria = subscription.getCriteriaString();
|
||||||
|
try {
|
||||||
|
RuntimeResourceDefinition resourceDef = CacheWarmingSvcImpl.parseUrlResourceType(myCtx, criteria);
|
||||||
|
myMatchUrlService.translateMatchUrl(criteria, resourceDef);
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
throw new UnprocessableEntityException("Invalid subscription criteria submitted: "+criteria+" "+e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,25 +21,39 @@ package ca.uhn.fhir.jpa.subscription;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import org.hl7.fhir.r4.model.Subscription;
|
import org.hl7.fhir.r4.model.Subscription;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.messaging.MessageHandler;
|
import org.springframework.messaging.MessageHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
public abstract class BaseSubscriptionSubscriber implements MessageHandler {
|
public abstract class BaseSubscriptionSubscriber implements MessageHandler {
|
||||||
|
|
||||||
private final IFhirResourceDao<?> mySubscriptionDao;
|
@Autowired
|
||||||
|
DaoRegistry myDaoRegistry;
|
||||||
|
|
||||||
private final Subscription.SubscriptionChannelType myChannelType;
|
private final Subscription.SubscriptionChannelType myChannelType;
|
||||||
private final BaseSubscriptionInterceptor mySubscriptionInterceptor;
|
private final BaseSubscriptionInterceptor mySubscriptionInterceptor;
|
||||||
|
|
||||||
|
private IFhirResourceDao<?> mySubscriptionDao;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
public BaseSubscriptionSubscriber(IFhirResourceDao<?> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
|
public BaseSubscriptionSubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
|
||||||
mySubscriptionDao = theSubscriptionDao;
|
|
||||||
myChannelType = theChannelType;
|
myChannelType = theChannelType;
|
||||||
mySubscriptionInterceptor = theSubscriptionInterceptor;
|
mySubscriptionInterceptor = theSubscriptionInterceptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void setSubscriptionDao() {
|
||||||
|
mySubscriptionDao = myDaoRegistry.getResourceDao("Subscription");
|
||||||
|
}
|
||||||
|
|
||||||
public Subscription.SubscriptionChannelType getChannelType() {
|
public Subscription.SubscriptionChannelType getChannelType() {
|
||||||
return myChannelType;
|
return myChannelType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,16 +162,16 @@ public class SubscriptionActivatingSubscriber {
|
||||||
|
|
||||||
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
|
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
|
||||||
public void handleMessage(ResourceModifiedMessage.OperationTypeEnum theOperationType, IIdType theId, final IBaseResource theSubscription) throws MessagingException {
|
public void handleMessage(ResourceModifiedMessage.OperationTypeEnum theOperationType, IIdType theId, final IBaseResource theSubscription) throws MessagingException {
|
||||||
|
|
||||||
switch (theOperationType) {
|
|
||||||
case DELETE:
|
|
||||||
mySubscriptionInterceptor.unregisterSubscription(theId);
|
|
||||||
return;
|
|
||||||
case CREATE:
|
|
||||||
case UPDATE:
|
|
||||||
if (!theId.getResourceType().equals("Subscription")) {
|
if (!theId.getResourceType().equals("Subscription")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
switch (theOperationType) {
|
||||||
|
case DELETE:
|
||||||
|
mySubscriptionInterceptor.unregisterSubscription(theId);
|
||||||
|
break;
|
||||||
|
case CREATE:
|
||||||
|
case UPDATE:
|
||||||
|
mySubscriptionInterceptor.validateCriteria(theSubscription);
|
||||||
activateAndRegisterSubscriptionIfRequiredInTransaction(theSubscription);
|
activateAndRegisterSubscriptionIfRequiredInTransaction(theSubscription);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,16 +1,29 @@
|
||||||
package ca.uhn.fhir.jpa.subscription;
|
package ca.uhn.fhir.jpa.subscription;
|
||||||
|
|
||||||
import java.util.List;
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
||||||
|
import ca.uhn.fhir.jpa.dao.MatchUrlService;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.matcher.ISubscriptionMatcher;
|
||||||
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.Subscription;
|
import org.hl7.fhir.r4.model.Subscription;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.messaging.Message;
|
import org.springframework.messaging.Message;
|
||||||
import org.springframework.messaging.MessageChannel;
|
import org.springframework.messaging.MessageChannel;
|
||||||
import org.springframework.messaging.MessagingException;
|
import org.springframework.messaging.MessagingException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
|
@ -32,24 +45,18 @@ import org.springframework.messaging.MessagingException;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
@Component
|
||||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
@Scope("prototype")
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
|
||||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
|
||||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
|
||||||
import ca.uhn.fhir.jpa.subscription.matcher.ISubscriptionMatcher;
|
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|
||||||
|
|
||||||
public class SubscriptionCheckingSubscriber extends BaseSubscriptionSubscriber {
|
public class SubscriptionCheckingSubscriber extends BaseSubscriptionSubscriber {
|
||||||
private Logger ourLog = LoggerFactory.getLogger(SubscriptionCheckingSubscriber.class);
|
private Logger ourLog = LoggerFactory.getLogger(SubscriptionCheckingSubscriber.class);
|
||||||
|
|
||||||
private final ISubscriptionMatcher mySubscriptionMatcher;
|
private final ISubscriptionMatcher mySubscriptionMatcher;
|
||||||
|
|
||||||
public SubscriptionCheckingSubscriber(IFhirResourceDao theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor, ISubscriptionMatcher theSubscriptionMatcher) {
|
@Autowired
|
||||||
super(theSubscriptionDao, theChannelType, theSubscriptionInterceptor);
|
private MatchUrlService myMatchUrlService;
|
||||||
|
|
||||||
|
public SubscriptionCheckingSubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor, ISubscriptionMatcher theSubscriptionMatcher) {
|
||||||
|
super(theChannelType, theSubscriptionInterceptor);
|
||||||
this.mySubscriptionMatcher = theSubscriptionMatcher;
|
this.mySubscriptionMatcher = theSubscriptionMatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,11 +84,10 @@ public class SubscriptionCheckingSubscriber extends BaseSubscriptionSubscriber {
|
||||||
|
|
||||||
IIdType id = msg.getId(getContext());
|
IIdType id = msg.getId(getContext());
|
||||||
String resourceType = id.getResourceType();
|
String resourceType = id.getResourceType();
|
||||||
String resourceId = id.getIdPart();
|
|
||||||
|
|
||||||
List<CanonicalSubscription> subscriptions = getSubscriptionInterceptor().getRegisteredSubscriptions();
|
List<CanonicalSubscription> subscriptions = getSubscriptionInterceptor().getRegisteredSubscriptions();
|
||||||
|
|
||||||
ourLog.trace("Testing {} subscriptions for applicability");
|
ourLog.trace("Testing {} subscriptions for applicability", subscriptions.size());
|
||||||
|
|
||||||
for (CanonicalSubscription nextSubscription : subscriptions) {
|
for (CanonicalSubscription nextSubscription : subscriptions) {
|
||||||
|
|
||||||
|
@ -112,7 +118,7 @@ public class SubscriptionCheckingSubscriber extends BaseSubscriptionSubscriber {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mySubscriptionMatcher.match(nextCriteriaString, msg)) {
|
if (!mySubscriptionMatcher.match(nextCriteriaString, msg).matched()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +154,7 @@ public class SubscriptionCheckingSubscriber extends BaseSubscriptionSubscriber {
|
||||||
*/
|
*/
|
||||||
protected IBundleProvider performSearch(String theCriteria) {
|
protected IBundleProvider performSearch(String theCriteria) {
|
||||||
RuntimeResourceDefinition responseResourceDef = getSubscriptionDao().validateCriteriaAndReturnResourceDefinition(theCriteria);
|
RuntimeResourceDefinition responseResourceDef = getSubscriptionDao().validateCriteriaAndReturnResourceDefinition(theCriteria);
|
||||||
SearchParameterMap responseCriteriaUrl = BaseHapiFhirDao.translateMatchUrl(getSubscriptionDao(), getSubscriptionDao().getContext(), theCriteria, responseResourceDef);
|
SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(theCriteria, responseResourceDef);
|
||||||
|
|
||||||
RequestDetails req = new ServletSubRequestDetails();
|
RequestDetails req = new ServletSubRequestDetails();
|
||||||
req.setSubRequest(true);
|
req.setSubRequest(true);
|
||||||
|
|
|
@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.subscription.email;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
|
||||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionDeliverySubscriber;
|
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionDeliverySubscriber;
|
||||||
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
|
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
|
||||||
import ca.uhn.fhir.jpa.subscription.ResourceDeliveryMessage;
|
import ca.uhn.fhir.jpa.subscription.ResourceDeliveryMessage;
|
||||||
|
@ -28,19 +27,24 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.r4.model.Subscription;
|
import org.hl7.fhir.r4.model.Subscription;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.*;
|
import static org.apache.commons.lang3.StringUtils.*;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Scope("prototype")
|
||||||
|
|
||||||
public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliverySubscriber {
|
public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliverySubscriber {
|
||||||
private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringEmailSubscriber.class);
|
private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringEmailSubscriber.class);
|
||||||
|
|
||||||
private SubscriptionEmailInterceptor mySubscriptionEmailInterceptor;
|
private SubscriptionEmailInterceptor mySubscriptionEmailInterceptor;
|
||||||
|
|
||||||
public SubscriptionDeliveringEmailSubscriber(IFhirResourceDao<?> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, SubscriptionEmailInterceptor theSubscriptionEmailInterceptor) {
|
public SubscriptionDeliveringEmailSubscriber(Subscription.SubscriptionChannelType theChannelType, SubscriptionEmailInterceptor theSubscriptionEmailInterceptor) {
|
||||||
super(theSubscriptionDao, theChannelType, theSubscriptionEmailInterceptor);
|
super(theChannelType, theSubscriptionEmailInterceptor);
|
||||||
|
|
||||||
mySubscriptionEmailInterceptor = theSubscriptionEmailInterceptor;
|
mySubscriptionEmailInterceptor = theSubscriptionEmailInterceptor;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,22 @@ package ca.uhn.fhir.jpa.subscription.email;
|
||||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||||
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
|
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.messaging.MessageHandler;
|
import org.springframework.messaging.MessageHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: If you're going to use this, you need to provide a bean
|
||||||
|
* of type {@link ca.uhn.fhir.jpa.subscription.email.IEmailSender}
|
||||||
|
* in your own Spring config
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Lazy
|
||||||
public class SubscriptionEmailInterceptor extends BaseSubscriptionInterceptor {
|
public class SubscriptionEmailInterceptor extends BaseSubscriptionInterceptor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,11 +47,13 @@ public class SubscriptionEmailInterceptor extends BaseSubscriptionInterceptor {
|
||||||
*/
|
*/
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private IEmailSender myEmailSender;
|
private IEmailSender myEmailSender;
|
||||||
|
@Autowired
|
||||||
|
BeanFactory myBeanFactory;
|
||||||
private String myDefaultFromAddress = "noreply@unknown.com";
|
private String myDefaultFromAddress = "noreply@unknown.com";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Optional<MessageHandler> createDeliveryHandler(CanonicalSubscription theSubscription) {
|
protected Optional<MessageHandler> createDeliveryHandler(CanonicalSubscription theSubscription) {
|
||||||
return Optional.of(new SubscriptionDeliveringEmailSubscriber(getSubscriptionDao(), getChannelType(), this));
|
return Optional.of(myBeanFactory.getBean(SubscriptionDeliveringEmailSubscriber.class, getChannelType(), this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
package ca.uhn.fhir.jpa.subscription.matcher;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
|
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
|
||||||
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.jpa.dao.index.ResourceIndexedSearchParams;
|
||||||
|
import ca.uhn.fhir.jpa.dao.MatchUrlService;
|
||||||
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.param.BaseParamWithPrefix;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CriteriaResourceMatcher {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FhirContext myContext;
|
||||||
|
@Autowired
|
||||||
|
private MatchUrlService myMatchUrlService;
|
||||||
|
@Autowired
|
||||||
|
ISearchParamRegistry mySearchParamRegistry;
|
||||||
|
|
||||||
|
public SubscriptionMatchResult match(String theCriteria, RuntimeResourceDefinition theResourceDefinition, ResourceIndexedSearchParams theSearchParams) {
|
||||||
|
SearchParameterMap searchParameterMap;
|
||||||
|
try {
|
||||||
|
searchParameterMap = myMatchUrlService.translateMatchUrl(theCriteria, theResourceDefinition);
|
||||||
|
} catch (UnsupportedOperationException e) {
|
||||||
|
return new SubscriptionMatchResult(theCriteria);
|
||||||
|
}
|
||||||
|
searchParameterMap.clean();
|
||||||
|
if (searchParameterMap.getLastUpdated() != null) {
|
||||||
|
return new SubscriptionMatchResult(Constants.PARAM_LASTUPDATED, "Qualifiers not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<List<? extends IQueryParameterType>>> entry : searchParameterMap.entrySet()) {
|
||||||
|
String theParamName = entry.getKey();
|
||||||
|
List<List<? extends IQueryParameterType>> theAndOrParams = entry.getValue();
|
||||||
|
SubscriptionMatchResult result = matchIdsWithAndOr(theParamName, theAndOrParams, theResourceDefinition, theSearchParams);
|
||||||
|
if (!result.matched()){
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SubscriptionMatchResult.MATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method is modelled from SearchBuilder.searchForIdsWithAndOr()
|
||||||
|
private SubscriptionMatchResult matchIdsWithAndOr(String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams, RuntimeResourceDefinition theResourceDefinition, ResourceIndexedSearchParams theSearchParams) {
|
||||||
|
if (theAndOrParams.isEmpty()) {
|
||||||
|
return SubscriptionMatchResult.MATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasQualifiers(theAndOrParams)) {
|
||||||
|
|
||||||
|
return new SubscriptionMatchResult(theParamName, "Qualifiers not supported.");
|
||||||
|
|
||||||
|
}
|
||||||
|
if (hasPrefixes(theAndOrParams)) {
|
||||||
|
|
||||||
|
return new SubscriptionMatchResult(theParamName, "Prefixes not supported.");
|
||||||
|
|
||||||
|
}
|
||||||
|
if (hasChain(theAndOrParams)) {
|
||||||
|
return new SubscriptionMatchResult(theParamName, "Chained references are not supported");
|
||||||
|
}
|
||||||
|
if (theParamName.equals(IAnyResource.SP_RES_ID)) {
|
||||||
|
|
||||||
|
return new SubscriptionMatchResult(theParamName);
|
||||||
|
|
||||||
|
} else if (theParamName.equals(IAnyResource.SP_RES_LANGUAGE)) {
|
||||||
|
|
||||||
|
return new SubscriptionMatchResult(theParamName);
|
||||||
|
|
||||||
|
} else if (theParamName.equals(Constants.PARAM_HAS)) {
|
||||||
|
|
||||||
|
return new SubscriptionMatchResult(theParamName);
|
||||||
|
|
||||||
|
} else if (theParamName.equals(Constants.PARAM_TAG) || theParamName.equals(Constants.PARAM_PROFILE) || theParamName.equals(Constants.PARAM_SECURITY)) {
|
||||||
|
|
||||||
|
return new SubscriptionMatchResult(theParamName);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
String resourceName = theResourceDefinition.getName();
|
||||||
|
RuntimeSearchParam paramDef = mySearchParamRegistry.getActiveSearchParam(resourceName, theParamName);
|
||||||
|
return matchResourceParam(theParamName, theAndOrParams, theSearchParams, resourceName, paramDef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubscriptionMatchResult matchResourceParam(String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams, ResourceIndexedSearchParams theSearchParams, String theResourceName, RuntimeSearchParam theParamDef) {
|
||||||
|
if (theParamDef != null) {
|
||||||
|
switch (theParamDef.getParamType()) {
|
||||||
|
case QUANTITY:
|
||||||
|
case TOKEN:
|
||||||
|
case STRING:
|
||||||
|
case NUMBER:
|
||||||
|
case URI:
|
||||||
|
case DATE:
|
||||||
|
case REFERENCE:
|
||||||
|
return new SubscriptionMatchResult(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theResourceName, theParamName, theParamDef, nextAnd, theSearchParams)));
|
||||||
|
case COMPOSITE:
|
||||||
|
case HAS:
|
||||||
|
case SPECIAL:
|
||||||
|
default:
|
||||||
|
return new SubscriptionMatchResult(theParamName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Constants.PARAM_CONTENT.equals(theParamName) || Constants.PARAM_TEXT.equals(theParamName)) {
|
||||||
|
return new SubscriptionMatchResult(theParamName);
|
||||||
|
} else {
|
||||||
|
throw new InvalidRequestException("Unknown search parameter " + theParamName + " for resource type " + theResourceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matchParams(String theResourceName, String theParamName, RuntimeSearchParam paramDef, List<? extends IQueryParameterType> theNextAnd, ResourceIndexedSearchParams theSearchParams) {
|
||||||
|
return theNextAnd.stream().anyMatch(token -> theSearchParams.matchParam(theResourceName, theParamName, paramDef, token));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasChain(List<List<? extends IQueryParameterType>> theAndOrParams) {
|
||||||
|
return theAndOrParams.stream().flatMap(List::stream).anyMatch(param -> param instanceof ReferenceParam && ((ReferenceParam)param).getChain() != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasQualifiers(List<List<? extends IQueryParameterType>> theAndOrParams) {
|
||||||
|
return theAndOrParams.stream().flatMap(List::stream).anyMatch(param -> param.getQueryParameterQualifier() != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasPrefixes(List<List<? extends IQueryParameterType>> theAndOrParams) {
|
||||||
|
Predicate<IQueryParameterType> hasPrefixPredicate = param -> param instanceof BaseParamWithPrefix &&
|
||||||
|
((BaseParamWithPrefix) param).getPrefix() != null;
|
||||||
|
return theAndOrParams.stream().flatMap(List::stream).anyMatch(hasPrefixPredicate);
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,5 +23,5 @@ package ca.uhn.fhir.jpa.subscription.matcher;
|
||||||
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
||||||
|
|
||||||
public interface ISubscriptionMatcher {
|
public interface ISubscriptionMatcher {
|
||||||
boolean match(String criteria, ResourceModifiedMessage msg);
|
SubscriptionMatchResult match(String criteria, ResourceModifiedMessage msg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package ca.uhn.fhir.jpa.subscription.matcher;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
||||||
|
|
||||||
|
public interface ISubscriptionMatcher {
|
||||||
|
boolean match(String criteria, ResourceModifiedMessage msg);
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package ca.uhn.fhir.jpa.subscription.matcher;
|
||||||
|
|
||||||
|
public class SubscriptionMatchResult {
|
||||||
|
// This could be an enum, but we may want to include details about unsupported matches in the future
|
||||||
|
public static final SubscriptionMatchResult MATCH = new SubscriptionMatchResult(true);
|
||||||
|
public static final SubscriptionMatchResult NO_MATCH = new SubscriptionMatchResult(false);
|
||||||
|
|
||||||
|
private final boolean myMatch;
|
||||||
|
private final boolean mySupported;
|
||||||
|
private final String myUnsupportedParameter;
|
||||||
|
private final String myUnsupportedReason;
|
||||||
|
|
||||||
|
public SubscriptionMatchResult(boolean theMatch) {
|
||||||
|
this.myMatch = theMatch;
|
||||||
|
this.mySupported = true;
|
||||||
|
this.myUnsupportedParameter = null;
|
||||||
|
this.myUnsupportedReason = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubscriptionMatchResult(String theUnsupportedParameter) {
|
||||||
|
this.myMatch = false;
|
||||||
|
this.mySupported = false;
|
||||||
|
this.myUnsupportedParameter = theUnsupportedParameter;
|
||||||
|
this.myUnsupportedReason = "Parameter not supported";
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubscriptionMatchResult(String theUnsupportedParameter, String theUnsupportedReason) {
|
||||||
|
this.myMatch = false;
|
||||||
|
this.mySupported = false;
|
||||||
|
this.myUnsupportedParameter = theUnsupportedParameter;
|
||||||
|
this.myUnsupportedReason = theUnsupportedReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supported() {
|
||||||
|
return mySupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matched() {
|
||||||
|
return myMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnsupportedReason() {
|
||||||
|
return "Parameter: <" + myUnsupportedParameter + "> Reason: " + myUnsupportedReason;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package ca.uhn.fhir.jpa.subscription.matcher;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SubscriptionMatcherCompositeInMemoryDatabase implements ISubscriptionMatcher {
|
||||||
|
private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherCompositeInMemoryDatabase.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
SubscriptionMatcherDatabase mySubscriptionMatcherDatabase;
|
||||||
|
@Autowired
|
||||||
|
SubscriptionMatcherInMemory mySubscriptionMatcherInMemory;
|
||||||
|
@Autowired
|
||||||
|
DaoConfig myDaoConfig;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubscriptionMatchResult match(String criteria, ResourceModifiedMessage msg) {
|
||||||
|
SubscriptionMatchResult result;
|
||||||
|
if (myDaoConfig.isEnableInMemorySubscriptionMatching()) {
|
||||||
|
result = mySubscriptionMatcherInMemory.match(criteria, msg);
|
||||||
|
if (!result.supported()) {
|
||||||
|
ourLog.info("Criteria {} not supported by InMemoryMatcher: {}. Reverting to DatabaseMatcher", criteria, result.getUnsupportedReason());
|
||||||
|
result = mySubscriptionMatcherDatabase.match(criteria, msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = mySubscriptionMatcherDatabase.match(criteria, msg);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,37 +20,39 @@ package ca.uhn.fhir.jpa.subscription.matcher;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
|
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||||
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
||||||
|
import ca.uhn.fhir.jpa.dao.MatchUrlService;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
||||||
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
@Service
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
@Lazy
|
||||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
|
||||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
|
||||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
|
||||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
|
||||||
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|
||||||
|
|
||||||
public class SubscriptionMatcherDatabase implements ISubscriptionMatcher {
|
public class SubscriptionMatcherDatabase implements ISubscriptionMatcher {
|
||||||
private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherDatabase.class);
|
private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherDatabase.class);
|
||||||
|
|
||||||
private final IFhirResourceDao<?> mySubscriptionDao;
|
@Autowired
|
||||||
|
private FhirContext myCtx;
|
||||||
private final BaseSubscriptionInterceptor mySubscriptionInterceptor;
|
@Autowired
|
||||||
|
DaoRegistry myDaoRegistry;
|
||||||
public SubscriptionMatcherDatabase(IFhirResourceDao<?> theSubscriptionDao, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
|
@Autowired
|
||||||
mySubscriptionDao = theSubscriptionDao;
|
MatchUrlService myMatchUrlService;
|
||||||
mySubscriptionInterceptor = theSubscriptionInterceptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean match(String criteria, ResourceModifiedMessage msg) {
|
public SubscriptionMatchResult match(String criteria, ResourceModifiedMessage msg) {
|
||||||
IIdType id = msg.getId(getContext());
|
IIdType id = msg.getId(myCtx);
|
||||||
String resourceType = id.getResourceType();
|
String resourceType = id.getResourceType();
|
||||||
String resourceId = id.getIdPart();
|
String resourceId = id.getIdPart();
|
||||||
|
|
||||||
|
@ -61,27 +63,23 @@ public class SubscriptionMatcherDatabase implements ISubscriptionMatcher {
|
||||||
|
|
||||||
ourLog.debug("Subscription check found {} results for query: {}", results.size(), criteria);
|
ourLog.debug("Subscription check found {} results for query: {}", results.size(), criteria);
|
||||||
|
|
||||||
return results.size() > 0;
|
return new SubscriptionMatchResult(results.size() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search based on a query criteria
|
* Search based on a query criteria
|
||||||
*/
|
*/
|
||||||
protected IBundleProvider performSearch(String theCriteria) {
|
protected IBundleProvider performSearch(String theCriteria) {
|
||||||
RuntimeResourceDefinition responseResourceDef = mySubscriptionDao.validateCriteriaAndReturnResourceDefinition(theCriteria);
|
IFhirResourceDao<?> subscriptionDao = myDaoRegistry.getResourceDao("Subscription");
|
||||||
SearchParameterMap responseCriteriaUrl = BaseHapiFhirDao.translateMatchUrl(mySubscriptionDao, getContext(), theCriteria, responseResourceDef);
|
RuntimeResourceDefinition responseResourceDef = subscriptionDao.validateCriteriaAndReturnResourceDefinition(theCriteria);
|
||||||
|
SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(theCriteria, responseResourceDef);
|
||||||
|
|
||||||
RequestDetails req = new ServletSubRequestDetails();
|
RequestDetails req = new ServletSubRequestDetails();
|
||||||
req.setSubRequest(true);
|
req.setSubRequest(true);
|
||||||
|
|
||||||
IFhirResourceDao<? extends IBaseResource> responseDao = mySubscriptionInterceptor.getDao(responseResourceDef.getImplementingClass());
|
IFhirResourceDao<? extends IBaseResource> responseDao = myDaoRegistry.getResourceDao(responseResourceDef.getImplementingClass());
|
||||||
responseCriteriaUrl.setLoadSynchronousUpTo(1);
|
responseCriteriaUrl.setLoadSynchronousUpTo(1);
|
||||||
|
|
||||||
IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req);
|
return responseDao.search(responseCriteriaUrl, req);
|
||||||
return responseResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FhirContext getContext() {
|
|
||||||
return mySubscriptionDao.getContext();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,13 +20,43 @@ package ca.uhn.fhir.jpa.subscription.matcher;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
|
import ca.uhn.fhir.jpa.dao.index.ResourceIndexedSearchParams;
|
||||||
|
import ca.uhn.fhir.jpa.dao.index.SearchParamExtractorService;
|
||||||
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Lazy
|
||||||
public class SubscriptionMatcherInMemory implements ISubscriptionMatcher {
|
public class SubscriptionMatcherInMemory implements ISubscriptionMatcher {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FhirContext myContext;
|
||||||
|
@Autowired
|
||||||
|
private CriteriaResourceMatcher myCriteriaResourceMatcher;
|
||||||
|
@Autowired
|
||||||
|
private SearchParamExtractorService mySearchParamExtractorService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean match(String criteria, ResourceModifiedMessage msg) {
|
public SubscriptionMatchResult match(String criteria, ResourceModifiedMessage msg) {
|
||||||
// FIXME KHS implement
|
return match(criteria, msg.getNewPayload(myContext));
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
SubscriptionMatchResult match(String criteria, IBaseResource resource) {
|
||||||
|
ResourceTable entity = new ResourceTable();
|
||||||
|
String resourceType = myContext.getResourceDefinition(resource).getName();
|
||||||
|
entity.setResourceType(resourceType);
|
||||||
|
ResourceIndexedSearchParams searchParams = new ResourceIndexedSearchParams();
|
||||||
|
mySearchParamExtractorService.extractFromResource(searchParams, entity, resource);
|
||||||
|
mySearchParamExtractorService.extractInlineReferences(resource);
|
||||||
|
mySearchParamExtractorService.extractResourceLinks(searchParams, entity, resource, resource.getMeta().getLastUpdated(), false);
|
||||||
|
RuntimeResourceDefinition resourceDefinition = myContext.getResourceDefinition(resource);
|
||||||
|
return myCriteriaResourceMatcher.match(criteria, resourceDefinition, searchParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,10 @@ package ca.uhn.fhir.jpa.subscription.resthook;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.subscription.*;
|
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionDeliverySubscriber;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.ResourceDeliveryMessage;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.client.api.*;
|
import ca.uhn.fhir.rest.client.api.*;
|
||||||
|
@ -35,7 +38,9 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.Subscription;
|
import org.hl7.fhir.r4.model.Subscription;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.messaging.MessagingException;
|
import org.springframework.messaging.MessagingException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -45,11 +50,13 @@ import java.util.Map;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Scope("prototype")
|
||||||
public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDeliverySubscriber {
|
public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDeliverySubscriber {
|
||||||
private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringRestHookSubscriber.class);
|
private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringRestHookSubscriber.class);
|
||||||
|
|
||||||
public SubscriptionDeliveringRestHookSubscriber(IFhirResourceDao<?> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
|
public SubscriptionDeliveringRestHookSubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
|
||||||
super(theSubscriptionDao, theChannelType, theSubscriptionInterceptor);
|
super(theChannelType, theSubscriptionInterceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void deliverPayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, EncodingEnum thePayloadType, IGenericClient theClient) {
|
protected void deliverPayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, EncodingEnum thePayloadType, IGenericClient theClient) {
|
||||||
|
|
|
@ -24,16 +24,25 @@ import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||||
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
|
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.messaging.MessageHandler;
|
import org.springframework.messaging.MessageHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Lazy
|
||||||
public class SubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor {
|
public class SubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionRestHookInterceptor.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionRestHookInterceptor.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
BeanFactory myBeanFactory;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Optional<MessageHandler> createDeliveryHandler(CanonicalSubscription theSubscription) {
|
protected Optional<MessageHandler> createDeliveryHandler(CanonicalSubscription theSubscription) {
|
||||||
SubscriptionDeliveringRestHookSubscriber value = new SubscriptionDeliveringRestHookSubscriber(getSubscriptionDao(), getChannelType(), this);
|
SubscriptionDeliveringRestHookSubscriber value = myBeanFactory.getBean(SubscriptionDeliveringRestHookSubscriber.class, getChannelType(), this);
|
||||||
return Optional.of(value);
|
return Optional.of(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,11 +26,15 @@ import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||||
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
|
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
|
||||||
import org.hl7.fhir.r4.model.Subscription;
|
import org.hl7.fhir.r4.model.Subscription;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.messaging.MessageHandler;
|
import org.springframework.messaging.MessageHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Lazy
|
||||||
public class SubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor {
|
public class SubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
|
@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.term;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
import ca.uhn.fhir.jpa.entity.TermConcept;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.util.CoverageIgnore;
|
import ca.uhn.fhir.util.CoverageIgnore;
|
||||||
|
@ -21,9 +20,6 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.persistence.PersistenceContext;
|
|
||||||
import javax.persistence.PersistenceContextType;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -53,12 +49,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implements IValidationSupport, IHapiTerminologySvcDstu3 {
|
public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implements IValidationSupport, IHapiTerminologySvcDstu3 {
|
||||||
|
|
||||||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
|
||||||
protected EntityManager myEntityManager;
|
|
||||||
@Autowired
|
|
||||||
protected FhirContext myContext;
|
|
||||||
@Autowired
|
|
||||||
protected ITermCodeSystemDao myCodeSystemDao;
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("myValueSetDaoDstu3")
|
@Qualifier("myValueSetDaoDstu3")
|
||||||
private IFhirResourceDao<ValueSet> myValueSetResourceDao;
|
private IFhirResourceDao<ValueSet> myValueSetResourceDao;
|
||||||
|
|
|
@ -2,7 +2,6 @@ package ca.uhn.fhir.jpa.term;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
|
|
||||||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
import ca.uhn.fhir.jpa.entity.TermConcept;
|
||||||
import ca.uhn.fhir.util.CoverageIgnore;
|
import ca.uhn.fhir.util.CoverageIgnore;
|
||||||
import ca.uhn.fhir.util.UrlUtil;
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
|
@ -20,9 +19,6 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.persistence.PersistenceContext;
|
|
||||||
import javax.persistence.PersistenceContextType;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -51,10 +47,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements IHapiTerminologySvcR4 {
|
public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements IHapiTerminologySvcR4 {
|
||||||
@Autowired
|
|
||||||
protected ITermCodeSystemDao myCodeSystemDao;
|
|
||||||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
|
||||||
protected EntityManager myEntityManager;
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("myConceptMapDaoR4")
|
@Qualifier("myConceptMapDaoR4")
|
||||||
private IFhirResourceDao<ConceptMap> myConceptMapResourceDao;
|
private IFhirResourceDao<ConceptMap> myConceptMapResourceDao;
|
||||||
|
@ -68,8 +60,6 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements
|
||||||
private IValidationSupport myValidationSupport;
|
private IValidationSupport myValidationSupport;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IHapiTerminologySvc myTerminologySvc;
|
private IHapiTerminologySvc myTerminologySvc;
|
||||||
@Autowired
|
|
||||||
private FhirContext myContext;
|
|
||||||
|
|
||||||
private void addAllChildren(String theSystemString, ConceptDefinitionComponent theCode, List<VersionIndependentConcept> theListToPopulate) {
|
private void addAllChildren(String theSystemString, ConceptDefinitionComponent theCode, List<VersionIndependentConcept> theListToPopulate) {
|
||||||
if (isNotBlank(theCode.getCode())) {
|
if (isNotBlank(theCode.getCode())) {
|
||||||
|
|
|
@ -188,7 +188,7 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UploadStatistics loadLoinc(List<FileDescriptor> theFiles, RequestDetails theRequestDetails) {
|
public UploadStatistics loadLoinc(List<FileDescriptor> theFiles, RequestDetails theRequestDetails) {
|
||||||
LoadedFileDescriptors descriptors = new LoadedFileDescriptors(theFiles);
|
try (LoadedFileDescriptors descriptors = new LoadedFileDescriptors(theFiles)) {
|
||||||
List<String> mandatoryFilenameFragments = Arrays.asList(
|
List<String> mandatoryFilenameFragments = Arrays.asList(
|
||||||
LOINC_FILE,
|
LOINC_FILE,
|
||||||
LOINC_HIERARCHY_FILE,
|
LOINC_HIERARCHY_FILE,
|
||||||
|
@ -216,10 +216,11 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc {
|
||||||
|
|
||||||
return processLoincFiles(descriptors, theRequestDetails);
|
return processLoincFiles(descriptors, theRequestDetails);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UploadStatistics loadSnomedCt(List<FileDescriptor> theFiles, RequestDetails theRequestDetails) {
|
public UploadStatistics loadSnomedCt(List<FileDescriptor> theFiles, RequestDetails theRequestDetails) {
|
||||||
LoadedFileDescriptors descriptors = new LoadedFileDescriptors(theFiles);
|
try (LoadedFileDescriptors descriptors = new LoadedFileDescriptors(theFiles)) {
|
||||||
|
|
||||||
List<String> expectedFilenameFragments = Arrays.asList(
|
List<String> expectedFilenameFragments = Arrays.asList(
|
||||||
SCT_FILE_DESCRIPTION,
|
SCT_FILE_DESCRIPTION,
|
||||||
|
@ -231,6 +232,7 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc {
|
||||||
|
|
||||||
return processSnomedCtFiles(descriptors, theRequestDetails);
|
return processSnomedCtFiles(descriptors, theRequestDetails);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UploadStatistics processLoincFiles(LoadedFileDescriptors theDescriptors, RequestDetails theRequestDetails) {
|
UploadStatistics processLoincFiles(LoadedFileDescriptors theDescriptors, RequestDetails theRequestDetails) {
|
||||||
final TermCodeSystemVersion codeSystemVersion = new TermCodeSystemVersion();
|
final TermCodeSystemVersion codeSystemVersion = new TermCodeSystemVersion();
|
||||||
|
|
|
@ -1,42 +1,48 @@
|
||||||
package ca.uhn.fhir.jpa.dao;
|
package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
|
import ca.uhn.fhir.jpa.config.TestR4Config;
|
||||||
|
import ca.uhn.fhir.model.dstu2.composite.PeriodDt;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Condition;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||||
|
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import org.junit.AfterClass;
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
import org.junit.Test;
|
@ContextConfiguration(classes = {TestR4Config.class})
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
|
||||||
import ca.uhn.fhir.model.dstu2.composite.PeriodDt;
|
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Condition;
|
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
|
||||||
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
|
||||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
|
||||||
|
|
||||||
public class BaseHapiFhirDaoTest extends BaseJpaTest {
|
public class BaseHapiFhirDaoTest extends BaseJpaTest {
|
||||||
|
|
||||||
private static FhirContext ourCtx = FhirContext.forDstu2();
|
private static FhirContext ourCtx = FhirContext.forDstu2();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MatchUrlService myMatchUrlService;
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
public static void afterClassClearContext() {
|
public static void afterClassClearContext() {
|
||||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTranslateMatchUrl() {
|
public void testTranslateMatchUrl() {
|
||||||
RuntimeResourceDefinition resourceDef = ourCtx.getResourceDefinition(Condition.class);
|
RuntimeResourceDefinition resourceDef = ourCtx.getResourceDefinition(Condition.class);
|
||||||
|
ISearchParamRegistry searchParamRegistry = mock(ISearchParamRegistry.class);
|
||||||
IDao dao = mock(IDao.class);
|
when(searchParamRegistry.getSearchParamByName(any(RuntimeResourceDefinition.class), eq("patient"))).thenReturn(resourceDef.getSearchParam("patient"));
|
||||||
when(dao.getSearchParamByName(any(RuntimeResourceDefinition.class), eq("patient"))).thenReturn(resourceDef.getSearchParam("patient"));
|
SearchParameterMap match = myMatchUrlService.translateMatchUrl("Condition?patient=304&_lastUpdated=>2011-01-01T11:12:21.0000Z", resourceDef);
|
||||||
|
|
||||||
SearchParameterMap match = BaseHapiFhirDao.translateMatchUrl(dao, ourCtx, "Condition?patient=304&_lastUpdated=>2011-01-01T11:12:21.0000Z", resourceDef);
|
|
||||||
assertEquals("2011-01-01T11:12:21.0000Z", match.getLastUpdated().getLowerBound().getValueAsString());
|
assertEquals("2011-01-01T11:12:21.0000Z", match.getLastUpdated().getLowerBound().getValueAsString());
|
||||||
assertEquals(ReferenceParam.class, match.get("patient").get(0).get(0).getClass());
|
assertEquals(ReferenceParam.class, match.get("patient").get(0).get(0).getClass());
|
||||||
assertEquals("304", ((ReferenceParam)match.get("patient").get(0).get(0)).getIdPart());
|
assertEquals("304", ((ReferenceParam)match.get("patient").get(0).get(0)).getIdPart());
|
||||||
|
|
|
@ -252,6 +252,8 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
|
||||||
protected ITermConceptMapGroupElementTargetDao myTermConceptMapGroupElementTargetDao;
|
protected ITermConceptMapGroupElementTargetDao myTermConceptMapGroupElementTargetDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
private JpaValidationSupportChainDstu3 myJpaValidationSupportChainDstu3;
|
private JpaValidationSupportChainDstu3 myJpaValidationSupportChainDstu3;
|
||||||
|
@Autowired
|
||||||
|
protected ISearchParamRegistry mySearchParamRegistry;
|
||||||
|
|
||||||
@After()
|
@After()
|
||||||
public void afterCleanupDao() {
|
public void afterCleanupDao() {
|
||||||
|
|
|
@ -2,10 +2,7 @@ package ca.uhn.fhir.jpa.dao.dstu3;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
|
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
|
||||||
|
@ -80,6 +77,16 @@ public class SearchParamExtractorDstu3Test {
|
||||||
public void requestRefresh() {
|
public void requestRefresh() {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new DaoConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
|
SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new DaoConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
|
||||||
|
|
|
@ -22,10 +22,7 @@ import org.junit.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
@ -85,6 +82,16 @@ public class SearchParamExtractorR4Test {
|
||||||
public void requestRefresh() {
|
public void requestRefresh() {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.config.TestR4Config;
|
||||||
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.rest.param.HasParam;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration(classes = {TestR4Config.class})
|
||||||
|
public class SearchParameterMapTest {
|
||||||
|
@Autowired
|
||||||
|
FhirContext myContext;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toNormalizedQueryStringTest() {
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add("_has", new HasParam("Observation", "subject", "identifier", "urn:system|FOO"));
|
||||||
|
String criteria = params.toNormalizedQueryString(myContext);
|
||||||
|
assertEquals(criteria, "?_has:Observation:identifier:urn:system|FOO=urn%3Asystem%7CFOO");
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,9 +39,10 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(EmailSubscriptionDstu2Test.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(EmailSubscriptionDstu2Test.class);
|
||||||
private static GreenMail ourTestSmtp;
|
private static GreenMail ourTestSmtp;
|
||||||
private static int ourListenerPort;
|
private static int ourListenerPort;
|
||||||
private SubscriptionEmailInterceptor mySubscriber;
|
|
||||||
private List<IIdType> mySubscriptionIds = new ArrayList<>();
|
private List<IIdType> mySubscriptionIds = new ArrayList<>();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SubscriptionEmailInterceptor mySubscriber;
|
||||||
@Autowired
|
@Autowired
|
||||||
private List<IFhirResourceDao<?>> myResourceDaos;
|
private List<IFhirResourceDao<?>> myResourceDaos;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -70,7 +71,6 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
|
||||||
emailSender.setSmtpServerPort(ourListenerPort);
|
emailSender.setSmtpServerPort(ourListenerPort);
|
||||||
emailSender.start();
|
emailSender.start();
|
||||||
|
|
||||||
mySubscriber = new SubscriptionEmailInterceptor();
|
|
||||||
mySubscriber.setEmailSender(emailSender);
|
mySubscriber.setEmailSender(emailSender);
|
||||||
mySubscriber.setResourceDaos(myResourceDaos);
|
mySubscriber.setResourceDaos(myResourceDaos);
|
||||||
mySubscriber.setFhirContext(myFhirCtx);
|
mySubscriber.setFhirContext(myFhirCtx);
|
||||||
|
|
|
@ -0,0 +1,486 @@
|
||||||
|
package ca.uhn.fhir.jpa.subscription.matcher;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
|
||||||
|
import org.hl7.fhir.dstu3.model.*;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.hl7.fhir.r4.model.codesystems.MedicationRequestCategory;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class SubscriptionMatcherInMemoryTestR3 extends BaseResourceProviderDstu3Test {
|
||||||
|
@Autowired
|
||||||
|
SubscriptionMatcherInMemory mySubscriptionMatcherInMemory;
|
||||||
|
|
||||||
|
private void assertUnsupported(IBaseResource resource, String criteria) {
|
||||||
|
assertFalse(mySubscriptionMatcherInMemory.match(criteria, resource).supported());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMatched(IBaseResource resource, String criteria) {
|
||||||
|
SubscriptionMatchResult result = mySubscriptionMatcherInMemory.match(criteria, resource);
|
||||||
|
;
|
||||||
|
assertTrue(result.supported());
|
||||||
|
assertTrue(result.matched());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNotMatched(IBaseResource resource, String criteria) {
|
||||||
|
SubscriptionMatchResult result = mySubscriptionMatcherInMemory.match(criteria, resource);
|
||||||
|
;
|
||||||
|
assertTrue(result.supported());
|
||||||
|
assertFalse(result.matched());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The following tests are copied from an e-mail from a site using HAPI FHIR
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQuestionnaireResponse() {
|
||||||
|
String criteria = "QuestionnaireResponse?questionnaire=HomeAbsenceHospitalizationRecord,ARIncenterAbsRecord";
|
||||||
|
|
||||||
|
{
|
||||||
|
QuestionnaireResponse qr = new QuestionnaireResponse();
|
||||||
|
qr.getQuestionnaire().setReference("Questionnaire/HomeAbsenceHospitalizationRecord");
|
||||||
|
assertMatched(qr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QuestionnaireResponse qr = new QuestionnaireResponse();
|
||||||
|
qr.getQuestionnaire().setReference("Questionnaire/Other");
|
||||||
|
assertNotMatched(qr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QuestionnaireResponse qr = new QuestionnaireResponse();
|
||||||
|
qr.getQuestionnaire().setDisplay("Questionnaire/HomeAbsenceHospitalizationRecord");
|
||||||
|
assertNotMatched(qr, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCommunicationRequest() {
|
||||||
|
String criteria = "CommunicationRequest?occurrence==2018-10-17";
|
||||||
|
|
||||||
|
{
|
||||||
|
CommunicationRequest cr = new CommunicationRequest();
|
||||||
|
cr.setOccurrence(new DateTimeType("2018-10-17"));
|
||||||
|
assertMatched(cr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CommunicationRequest cr = new CommunicationRequest();
|
||||||
|
cr.setOccurrence(new DateTimeType("2018-10-16"));
|
||||||
|
assertNotMatched(cr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CommunicationRequest cr = new CommunicationRequest();
|
||||||
|
cr.setOccurrence(new DateTimeType("2018-10-16"));
|
||||||
|
assertNotMatched(cr, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcedureRequest() {
|
||||||
|
String criteria = "ProcedureRequest?intent=original-order";
|
||||||
|
|
||||||
|
{
|
||||||
|
ProcedureRequest pr = new ProcedureRequest();
|
||||||
|
pr.setIntent(ProcedureRequest.ProcedureRequestIntent.ORIGINALORDER);
|
||||||
|
assertMatched(pr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ProcedureRequest pr = new ProcedureRequest();
|
||||||
|
pr.setIntent(ProcedureRequest.ProcedureRequestIntent.ORDER);
|
||||||
|
assertNotMatched(pr, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObservationContextTypeUnsupported() {
|
||||||
|
String criteria = "Observation?code=17861-6&context.type=IHD";
|
||||||
|
{
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getCode().addCoding().setCode("XXX");
|
||||||
|
assertNotMatched(obs, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getCode().addCoding().setCode("17861-6");
|
||||||
|
assertUnsupported(obs, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that it still fails fast even if the chained parameter is first
|
||||||
|
@Test
|
||||||
|
public void testObservationContextTypeUnsupportedReverse() {
|
||||||
|
String criteria = "Observation?context.type=IHD&code=17861-6";
|
||||||
|
{
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getCode().addCoding().setCode("XXX");
|
||||||
|
assertNotMatched(obs, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getCode().addCoding().setCode("17861-6");
|
||||||
|
assertUnsupported(obs, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void medicationRequestOutpatient() {
|
||||||
|
// Note the date== evaluates to date=eq which is a legacy format supported by hapi fhir
|
||||||
|
String criteria = "MedicationRequest?intent=instance-order&category=outpatient&date==2018-10-19";
|
||||||
|
|
||||||
|
{
|
||||||
|
MedicationRequest mr = new MedicationRequest();
|
||||||
|
mr.setIntent(MedicationRequest.MedicationRequestIntent.INSTANCEORDER);
|
||||||
|
mr.getCategory().addCoding().setCode(MedicationRequestCategory.OUTPATIENT.toCode());
|
||||||
|
Dosage dosage = new Dosage();
|
||||||
|
Timing timing = new Timing();
|
||||||
|
timing.getEvent().add(new DateTimeType("2018-10-19"));
|
||||||
|
dosage.setTiming(timing);
|
||||||
|
mr.getDosageInstruction().add(dosage);
|
||||||
|
assertMatched(mr, criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MedicationRequest mr = new MedicationRequest();
|
||||||
|
mr.setIntent(MedicationRequest.MedicationRequestIntent.INSTANCEORDER);
|
||||||
|
mr.getCategory().addCoding().setCode(MedicationRequestCategory.INPATIENT.toCode());
|
||||||
|
Dosage dosage = new Dosage();
|
||||||
|
Timing timing = new Timing();
|
||||||
|
timing.getEvent().add(new DateTimeType("2018-10-19"));
|
||||||
|
dosage.setTiming(timing);
|
||||||
|
mr.getDosageInstruction().add(dosage);
|
||||||
|
assertNotMatched(mr, criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MedicationRequest mr = new MedicationRequest();
|
||||||
|
mr.setIntent(MedicationRequest.MedicationRequestIntent.INSTANCEORDER);
|
||||||
|
mr.getCategory().addCoding().setCode(MedicationRequestCategory.OUTPATIENT.toCode());
|
||||||
|
Dosage dosage = new Dosage();
|
||||||
|
Timing timing = new Timing();
|
||||||
|
timing.getEvent().add(new DateTimeType("2018-10-20"));
|
||||||
|
dosage.setTiming(timing);
|
||||||
|
mr.getDosageInstruction().add(dosage);
|
||||||
|
assertNotMatched(mr, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMedicationRequestStatuses() {
|
||||||
|
String criteria = "MedicationRequest?intent=plan&category=outpatient&status=suspended,entered-in-error,cancelled,stopped";
|
||||||
|
|
||||||
|
// Note suspended is an invalid status and will never match
|
||||||
|
{
|
||||||
|
MedicationRequest mr = new MedicationRequest();
|
||||||
|
mr.setIntent(MedicationRequest.MedicationRequestIntent.PLAN);
|
||||||
|
mr.getCategory().addCoding().setCode(MedicationRequestCategory.OUTPATIENT.toCode());
|
||||||
|
mr.setStatus(MedicationRequest.MedicationRequestStatus.ENTEREDINERROR);
|
||||||
|
assertMatched(mr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
MedicationRequest mr = new MedicationRequest();
|
||||||
|
mr.setIntent(MedicationRequest.MedicationRequestIntent.PLAN);
|
||||||
|
mr.getCategory().addCoding().setCode(MedicationRequestCategory.OUTPATIENT.toCode());
|
||||||
|
mr.setStatus(MedicationRequest.MedicationRequestStatus.CANCELLED);
|
||||||
|
assertMatched(mr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
MedicationRequest mr = new MedicationRequest();
|
||||||
|
mr.setIntent(MedicationRequest.MedicationRequestIntent.PLAN);
|
||||||
|
mr.getCategory().addCoding().setCode(MedicationRequestCategory.OUTPATIENT.toCode());
|
||||||
|
mr.setStatus(MedicationRequest.MedicationRequestStatus.STOPPED);
|
||||||
|
assertMatched(mr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
MedicationRequest mr = new MedicationRequest();
|
||||||
|
mr.setIntent(MedicationRequest.MedicationRequestIntent.PLAN);
|
||||||
|
mr.getCategory().addCoding().setCode(MedicationRequestCategory.OUTPATIENT.toCode());
|
||||||
|
mr.setStatus(MedicationRequest.MedicationRequestStatus.ACTIVE);
|
||||||
|
assertNotMatched(mr, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBloodTest() {
|
||||||
|
String criteria = "Observation?code=FR_Org1Blood2nd,FR_Org1Blood3rd,FR_Org%201BldCult,FR_Org2Blood2nd,FR_Org2Blood3rd,FR_Org%202BldCult,FR_Org3Blood2nd,FR_Org3Blood3rd,FR_Org3BldCult,FR_Org4Blood2nd,FR_Org4Blood3rd,FR_Org4BldCult,FR_Org5Blood2nd,FR_Org5Blood3rd,FR_Org%205BldCult,FR_Org6Blood2nd,FR_Org6Blood3rd,FR_Org6BldCult,FR_Org7Blood2nd,FR_Org7Blood3rd,FR_Org7BldCult,FR_Org8Blood2nd,FR_Org8Blood3rd,FR_Org8BldCult,FR_Org9Blood2nd,FR_Org9Blood3rd,FR_Org9BldCult,FR_Bld2ndCulture,FR_Bld3rdCulture,FR_Blood%20Culture,FR_Com1Bld3rd,FR_Com1BldCult,FR_Com2Bld2nd,FR_Com2Bld3rd,FR_Com2BldCult,FR_CultureBld2nd,FR_CultureBld3rd,FR_CultureBldCul,FR_GmStainBldCul,FR_GramStain2Bld,FR_GramStain3Bld,FR_GramStNegBac&context.type=IHD";
|
||||||
|
|
||||||
|
{
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getCode().addCoding().setCode("FR_Org1Blood2nd");
|
||||||
|
assertUnsupported(obs, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getCode().addCoding().setCode("XXX");
|
||||||
|
assertNotMatched(obs, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcedureHemodialysis() {
|
||||||
|
String criteria = "Procedure?category=Hemodialysis";
|
||||||
|
|
||||||
|
{
|
||||||
|
Procedure proc = new Procedure();
|
||||||
|
proc.getCategory().addCoding().setCode("Hemodialysis");
|
||||||
|
assertMatched(proc, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Procedure proc = new Procedure();
|
||||||
|
proc.getCategory().addCoding().setCode("XXX");
|
||||||
|
assertNotMatched(proc, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcedureHDStandard() {
|
||||||
|
String criteria = "Procedure?code=HD_Standard&status=completed&location=Lab123";
|
||||||
|
|
||||||
|
{
|
||||||
|
Procedure proc = new Procedure();
|
||||||
|
proc.getCode().addCoding().setCode("HD_Standard");
|
||||||
|
proc.setStatus(Procedure.ProcedureStatus.COMPLETED);
|
||||||
|
IIdType locId = new IdType("Location", "Lab123");
|
||||||
|
proc.getLocation().setReference(locId.getValue());
|
||||||
|
assertMatched(proc, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Procedure proc = new Procedure();
|
||||||
|
proc.getCode().addCoding().setCode("HD_Standard");
|
||||||
|
proc.setStatus(Procedure.ProcedureStatus.COMPLETED);
|
||||||
|
IIdType locId = new IdType("Location", "XXX");
|
||||||
|
proc.getLocation().setReference(locId.getValue());
|
||||||
|
assertNotMatched(proc, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Procedure proc = new Procedure();
|
||||||
|
proc.getCode().addCoding().setCode("XXX");
|
||||||
|
proc.setStatus(Procedure.ProcedureStatus.COMPLETED);
|
||||||
|
IIdType locId = new IdType("Location", "Lab123");
|
||||||
|
proc.getLocation().setReference(locId.getValue());
|
||||||
|
assertNotMatched(proc, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProvenance() {
|
||||||
|
String criteria = "Provenance?activity=http://hl7.org/fhir/v3/DocumentCompletion%7CAU";
|
||||||
|
|
||||||
|
SearchParameter sp = new SearchParameter();
|
||||||
|
sp.addBase("Provenance");
|
||||||
|
sp.setCode("activity");
|
||||||
|
sp.setType(Enumerations.SearchParamType.TOKEN);
|
||||||
|
sp.setExpression("Provenance.activity");
|
||||||
|
sp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
|
||||||
|
sp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
|
||||||
|
mySearchParameterDao.create(sp);
|
||||||
|
mySearchParamRegsitry.forceRefresh();
|
||||||
|
|
||||||
|
{
|
||||||
|
Provenance prov = new Provenance();
|
||||||
|
prov.setActivity(new Coding().setSystem("http://hl7.org/fhir/v3/DocumentCompletion").setCode("AU"));
|
||||||
|
assertMatched(prov, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Provenance prov = new Provenance();
|
||||||
|
assertNotMatched(prov, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Provenance prov = new Provenance();
|
||||||
|
prov.setActivity(new Coding().setCode("XXX"));
|
||||||
|
assertNotMatched(prov, criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBodySite() {
|
||||||
|
String criteria = "BodySite?accessType=Catheter,PD%20Catheter";
|
||||||
|
|
||||||
|
SearchParameter sp = new SearchParameter();
|
||||||
|
sp.addBase("BodySite");
|
||||||
|
sp.setCode("accessType");
|
||||||
|
sp.setType(Enumerations.SearchParamType.TOKEN);
|
||||||
|
sp.setExpression("BodySite.extension('BodySite#accessType')");
|
||||||
|
sp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
|
||||||
|
sp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
|
||||||
|
mySearchParameterDao.create(sp);
|
||||||
|
mySearchParamRegsitry.forceRefresh();
|
||||||
|
|
||||||
|
{
|
||||||
|
BodySite bodySite = new BodySite();
|
||||||
|
bodySite.addExtension().setUrl("BodySite#accessType").setValue(new Coding().setCode("Catheter"));
|
||||||
|
assertMatched(bodySite, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BodySite bodySite = new BodySite();
|
||||||
|
bodySite.addExtension().setUrl("BodySite#accessType").setValue(new Coding().setCode("PD Catheter"));
|
||||||
|
assertMatched(bodySite, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BodySite bodySite = new BodySite();
|
||||||
|
assertNotMatched(bodySite, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BodySite bodySite = new BodySite();
|
||||||
|
bodySite.addExtension().setUrl("BodySite#accessType").setValue(new Coding().setCode("XXX"));
|
||||||
|
assertNotMatched(bodySite, criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcedureAnyLocation() {
|
||||||
|
String criteria = "Procedure?code=HD_Standard&status=completed";
|
||||||
|
{
|
||||||
|
Procedure proc = new Procedure();
|
||||||
|
proc.getCode().addCoding().setCode("HD_Standard");
|
||||||
|
proc.setStatus(Procedure.ProcedureStatus.COMPLETED);
|
||||||
|
IIdType locId = new IdType("Location", "Lab456");
|
||||||
|
proc.getLocation().setReference(locId.getValue());
|
||||||
|
assertMatched(proc, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Procedure proc = new Procedure();
|
||||||
|
proc.getCode().addCoding().setCode("HD_Standard");
|
||||||
|
proc.setStatus(Procedure.ProcedureStatus.ABORTED);
|
||||||
|
assertNotMatched(proc, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Procedure proc = new Procedure();
|
||||||
|
proc.getCode().addCoding().setCode("XXX");
|
||||||
|
proc.setStatus(Procedure.ProcedureStatus.COMPLETED);
|
||||||
|
assertNotMatched(proc, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQuestionnaireResponseLong() {
|
||||||
|
String criteria = "QuestionnaireResponse?questionnaire=HomeAbsenceHospitalizationRecord,ARIncenterAbsRecord,FMCSWDepressionSymptomsScreener,FMCAKIComprehensiveSW,FMCSWIntensiveScreener,FMCESRDComprehensiveSW,FMCNutritionProgressNote,FMCAKIComprehensiveRN";
|
||||||
|
|
||||||
|
{
|
||||||
|
QuestionnaireResponse qr = new QuestionnaireResponse();
|
||||||
|
qr.getQuestionnaire().setReference("Questionnaire/HomeAbsenceHospitalizationRecord");
|
||||||
|
assertMatched(qr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QuestionnaireResponse qr = new QuestionnaireResponse();
|
||||||
|
qr.getQuestionnaire().setReference("Questionnaire/FMCSWIntensiveScreener");
|
||||||
|
assertMatched(qr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QuestionnaireResponse qr = new QuestionnaireResponse();
|
||||||
|
qr.getQuestionnaire().setReference("Questionnaire/FMCAKIComprehensiveRN");
|
||||||
|
assertMatched(qr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QuestionnaireResponse qr = new QuestionnaireResponse();
|
||||||
|
assertNotMatched(qr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QuestionnaireResponse qr = new QuestionnaireResponse();
|
||||||
|
qr.getQuestionnaire().setReference("Questionnaire/FMCAKIComprehensiveRM");
|
||||||
|
assertNotMatched(qr, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcedureRequestCategory() {
|
||||||
|
String criteria = "ProcedureRequest?intent=instance-order&category=Laboratory,Ancillary%20Orders,Hemodialysis&occurrence==2018-10-19";
|
||||||
|
|
||||||
|
SearchParameter sp = new SearchParameter();
|
||||||
|
sp.addBase("ProcedureRequest");
|
||||||
|
sp.setCode("category");
|
||||||
|
sp.setType(Enumerations.SearchParamType.TOKEN);
|
||||||
|
sp.setExpression("ProcedureRequest.category");
|
||||||
|
sp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
|
||||||
|
sp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
|
||||||
|
mySearchParameterDao.create(sp);
|
||||||
|
mySearchParamRegsitry.forceRefresh();
|
||||||
|
|
||||||
|
{
|
||||||
|
ProcedureRequest pr = new ProcedureRequest();
|
||||||
|
pr.setIntent(ProcedureRequest.ProcedureRequestIntent.INSTANCEORDER);
|
||||||
|
CodeableConcept code = new CodeableConcept();
|
||||||
|
code.addCoding().setCode("Laboratory");
|
||||||
|
pr.getCategory().add(code);
|
||||||
|
pr.setOccurrence(new DateTimeType("2018-10-19"));
|
||||||
|
assertMatched(pr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ProcedureRequest pr = new ProcedureRequest();
|
||||||
|
pr.setIntent(ProcedureRequest.ProcedureRequestIntent.INSTANCEORDER);
|
||||||
|
CodeableConcept code = new CodeableConcept();
|
||||||
|
code.addCoding().setCode("Ancillary Orders");
|
||||||
|
pr.getCategory().add(code);
|
||||||
|
pr.setOccurrence(new DateTimeType("2018-10-19"));
|
||||||
|
assertMatched(pr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ProcedureRequest pr = new ProcedureRequest();
|
||||||
|
pr.setIntent(ProcedureRequest.ProcedureRequestIntent.INSTANCEORDER);
|
||||||
|
CodeableConcept code = new CodeableConcept();
|
||||||
|
code.addCoding().setCode("Hemodialysis");
|
||||||
|
pr.getCategory().add(code);
|
||||||
|
pr.setOccurrence(new DateTimeType("2018-10-19"));
|
||||||
|
assertMatched(pr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ProcedureRequest pr = new ProcedureRequest();
|
||||||
|
pr.setIntent(ProcedureRequest.ProcedureRequestIntent.INSTANCEORDER);
|
||||||
|
pr.setOccurrence(new DateTimeType("2018-10-19"));
|
||||||
|
assertNotMatched(pr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ProcedureRequest pr = new ProcedureRequest();
|
||||||
|
CodeableConcept code = new CodeableConcept();
|
||||||
|
code.addCoding().setCode("Hemodialysis");
|
||||||
|
pr.getCategory().add(code);
|
||||||
|
pr.setOccurrence(new DateTimeType("2018-10-19"));
|
||||||
|
assertNotMatched(pr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ProcedureRequest pr = new ProcedureRequest();
|
||||||
|
pr.setIntent(ProcedureRequest.ProcedureRequestIntent.INSTANCEORDER);
|
||||||
|
CodeableConcept code = new CodeableConcept();
|
||||||
|
code.addCoding().setCode("Hemodialysis");
|
||||||
|
pr.getCategory().add(code);
|
||||||
|
assertNotMatched(pr, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ProcedureRequest pr = new ProcedureRequest();
|
||||||
|
pr.setIntent(ProcedureRequest.ProcedureRequestIntent.INSTANCEORDER);
|
||||||
|
CodeableConcept code = new CodeableConcept();
|
||||||
|
code.addCoding().setCode("XXX");
|
||||||
|
pr.getCategory().add(code);
|
||||||
|
pr.setOccurrence(new DateTimeType("2018-10-19"));
|
||||||
|
assertNotMatched(pr, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEposideOfCare() {
|
||||||
|
String criteria = "EpisodeOfCare?status=active";
|
||||||
|
{
|
||||||
|
EpisodeOfCare eoc = new EpisodeOfCare();
|
||||||
|
eoc.setStatus(EpisodeOfCare.EpisodeOfCareStatus.ACTIVE);
|
||||||
|
assertMatched(eoc, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
EpisodeOfCare eoc = new EpisodeOfCare();
|
||||||
|
assertNotMatched(eoc, criteria);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
EpisodeOfCare eoc = new EpisodeOfCare();
|
||||||
|
eoc.setStatus(EpisodeOfCare.EpisodeOfCareStatus.CANCELLED);
|
||||||
|
assertNotMatched(eoc, criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These last two are covered by other tests above
|
||||||
|
// String criteria = "ProcedureRequest?intent=original-order&category=Laboratory,Ancillary%20Orders,Hemodialysis&status=suspended,entered-in-error,cancelled";
|
||||||
|
// String criteria = "Observation?code=70965-9&context.type=IHD";
|
||||||
|
}
|
|
@ -0,0 +1,889 @@
|
||||||
|
package ca.uhn.fhir.jpa.subscription.matcher;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.config.TestR4Config;
|
||||||
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
|
||||||
|
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||||
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.rest.param.*;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.hl7.fhir.r4.model.*;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration(classes = {TestR4Config.class})
|
||||||
|
public class SubscriptionMatcherInMemoryTestR4 {
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionMatcherInMemoryTestR4.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
SubscriptionMatcherInMemory mySubscriptionMatcherInMemory;
|
||||||
|
@Autowired
|
||||||
|
FhirContext myContext;
|
||||||
|
|
||||||
|
private SubscriptionMatchResult match(IBaseResource resource, SearchParameterMap params) {
|
||||||
|
String criteria = params.toNormalizedQueryString(myContext);
|
||||||
|
ourLog.info("Criteria: <{}>", criteria);
|
||||||
|
return mySubscriptionMatcherInMemory.match(criteria, resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertUnsupported(IBaseResource resource, SearchParameterMap params) {
|
||||||
|
assertFalse(match(resource, params).supported());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMatched(IBaseResource resource, SearchParameterMap params) {
|
||||||
|
SubscriptionMatchResult result = match(resource, params);
|
||||||
|
assertTrue(result.getUnsupportedReason(), result.supported());
|
||||||
|
assertTrue(result.matched());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNotMatched(IBaseResource resource, SearchParameterMap params) {
|
||||||
|
SubscriptionMatchResult result = match(resource, params);
|
||||||
|
assertTrue(result.getUnsupportedReason(), result.supported());
|
||||||
|
assertFalse(result.matched());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The following tests are copied from FhirResourceDaoR4SearchNoFtTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChainReferenceUnsupported() {
|
||||||
|
Encounter enc1 = new Encounter();
|
||||||
|
IIdType pid1 = new IdType("Patient", 1L);
|
||||||
|
enc1.getSubject().setReference(pid1.getValue());
|
||||||
|
|
||||||
|
SearchParameterMap map;
|
||||||
|
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "foo|bar").setChain("identifier"));
|
||||||
|
assertUnsupported(enc1, map);
|
||||||
|
|
||||||
|
MedicationAdministration ma = new MedicationAdministration();
|
||||||
|
IIdType mid1 = new IdType("Medication", 1L);
|
||||||
|
ma.setMedication(new Reference(mid1));
|
||||||
|
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
map.add(MedicationAdministration.SP_MEDICATION, new ReferenceAndListParam().addAnd(new ReferenceOrListParam().add(new ReferenceParam("code", "04823543"))));
|
||||||
|
assertUnsupported(ma, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHasParameterUnsupported() {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
||||||
|
patient.addName().setFamily("Tester").addGiven("Joe");
|
||||||
|
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add("_has", new HasParam("Observation", "subject", "identifier", "urn:system|FOO"));
|
||||||
|
String criteria = params.toNormalizedQueryString(myContext);
|
||||||
|
assertUnsupported(patient, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchCode() {
|
||||||
|
Subscription subs = new Subscription();
|
||||||
|
subs.setStatus(Subscription.SubscriptionStatus.ACTIVE);
|
||||||
|
subs.getChannel().setType(Subscription.SubscriptionChannelType.WEBSOCKET);
|
||||||
|
subs.setCriteria("Observation?");
|
||||||
|
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
assertMatched(subs, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Subscription.SP_TYPE, new TokenParam(null, Subscription.SubscriptionChannelType.WEBSOCKET.toCode()));
|
||||||
|
params.add(Subscription.SP_STATUS, new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode()));
|
||||||
|
assertMatched(subs, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Subscription.SP_TYPE, new TokenParam(null, Subscription.SubscriptionChannelType.WEBSOCKET.toCode()));
|
||||||
|
params.add(Subscription.SP_STATUS, new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode() + "2"));
|
||||||
|
assertNotMatched(subs, params);
|
||||||
|
// // Wrong param
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Subscription.SP_STATUS, new TokenParam(null, Subscription.SubscriptionChannelType.WEBSOCKET.toCode()));
|
||||||
|
assertNotMatched(subs, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchCompositeUnsupported() {
|
||||||
|
Observation o1 = new Observation();
|
||||||
|
o1.getCode().addCoding().setSystem("foo").setCode("testSearchCompositeParamN01");
|
||||||
|
o1.setValue(new StringType("testSearchCompositeParamS01"));
|
||||||
|
|
||||||
|
TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamN01");
|
||||||
|
StringParam v1 = new StringParam("testSearchCompositeParamS01");
|
||||||
|
CompositeParam<TokenParam, StringParam> val = new CompositeParam<TokenParam, StringParam>(v0, v1);
|
||||||
|
SearchParameterMap params = new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_CODE_VALUE_STRING, val);
|
||||||
|
assertUnsupported(o1, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComponentQuantityWithPrefixUnsupported() {
|
||||||
|
Observation o1 = new Observation();
|
||||||
|
o1.addComponent()
|
||||||
|
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code1")))
|
||||||
|
.setValue(new Quantity().setSystem("http://bar").setCode("code1").setValue(200));
|
||||||
|
o1.addComponent()
|
||||||
|
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code2")))
|
||||||
|
.setValue(new Quantity().setSystem("http://bar").setCode("code2").setValue(200));
|
||||||
|
|
||||||
|
String param = Observation.SP_COMPONENT_VALUE_QUANTITY;
|
||||||
|
QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 150, "http://bar", "code1");
|
||||||
|
SearchParameterMap params = new SearchParameterMap().setLoadSynchronous(true).add(param, v1);
|
||||||
|
assertUnsupported(o1, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComponentQuantityEquals() {
|
||||||
|
Observation o1 = new Observation();
|
||||||
|
o1.addComponent()
|
||||||
|
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code1")))
|
||||||
|
.setValue(new Quantity().setSystem("http://bar").setCode("code1").setValue(150));
|
||||||
|
o1.addComponent()
|
||||||
|
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code2")))
|
||||||
|
.setValue(new Quantity().setSystem("http://bar").setCode("code2").setValue(150));
|
||||||
|
|
||||||
|
String param = Observation.SP_COMPONENT_VALUE_QUANTITY;
|
||||||
|
|
||||||
|
QuantityParam v1 = new QuantityParam(null, 150, "http://bar", "code1");
|
||||||
|
SearchParameterMap params = new SearchParameterMap().setLoadSynchronous(true).add(param, v1);
|
||||||
|
assertMatched(o1, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIdNotSupported() {
|
||||||
|
Observation o1 = new Observation();
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add("_id", new StringParam("testSearchForUnknownAlphanumericId"));
|
||||||
|
assertUnsupported(o1, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLanguageNotSupported() {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.getLanguageElement().setValue("en_CA");
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
||||||
|
patient.addName().setFamily("testSearchLanguageParam").addGiven("Joe");
|
||||||
|
SearchParameterMap params;
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(IAnyResource.SP_RES_LANGUAGE, new StringParam("en_CA"));
|
||||||
|
assertUnsupported(patient, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchLastUpdatedParamUnsupported() throws InterruptedException {
|
||||||
|
String methodName = "testSearchLastUpdatedParam";
|
||||||
|
DateTimeType today = new DateTimeType(new Date(), TemporalPrecisionEnum.DAY);
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
||||||
|
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.setLastUpdated(new DateRangeParam(today, null));
|
||||||
|
assertUnsupported(patient, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchNameParam() {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
||||||
|
patient.addName().setFamily("testSearchNameParam01Fam").addGiven("testSearchNameParam01Giv");
|
||||||
|
|
||||||
|
SearchParameterMap params;
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("testSearchNameParam01Fam"));
|
||||||
|
assertMatched(patient, params);
|
||||||
|
|
||||||
|
// Given name shouldn't return for family param
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("testSearchNameParam01Giv"));
|
||||||
|
assertNotMatched(patient, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_NAME, new StringParam("testSearchNameParam01Fam"));
|
||||||
|
assertMatched(patient, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_NAME, new StringParam("testSearchNameParam01Giv"));
|
||||||
|
assertMatched(patient, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("testSearchNameParam01Foo"));
|
||||||
|
assertNotMatched(patient, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchNumberParam() {
|
||||||
|
RiskAssessment risk = new RiskAssessment();
|
||||||
|
risk.addIdentifier().setSystem("foo").setValue("testSearchNumberParam01");
|
||||||
|
risk.addPrediction().setProbability(new DecimalType(2));
|
||||||
|
|
||||||
|
SearchParameterMap params;
|
||||||
|
params = new SearchParameterMap().add(RiskAssessment.SP_PROBABILITY, new NumberParam(">1"));
|
||||||
|
assertUnsupported(risk, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap().add(RiskAssessment.SP_PROBABILITY, new NumberParam("<1"));
|
||||||
|
assertUnsupported(risk, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap().add(RiskAssessment.SP_PROBABILITY, new NumberParam("2"));
|
||||||
|
assertMatched(risk, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap().add(RiskAssessment.SP_PROBABILITY, new NumberParam("3"));
|
||||||
|
assertNotMatched(risk, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchNumberWrongParam() {
|
||||||
|
ImmunizationRecommendation ir1 = new ImmunizationRecommendation();
|
||||||
|
ir1.addRecommendation().setDoseNumber(new PositiveIntType(1));
|
||||||
|
|
||||||
|
SearchParameterMap params = new SearchParameterMap().add(ImmunizationRecommendation.SP_DOSE_NUMBER, new NumberParam("1"));
|
||||||
|
assertMatched(ir1, params);
|
||||||
|
params = new SearchParameterMap().add(ImmunizationRecommendation.SP_DOSE_SEQUENCE, new NumberParam("1"));
|
||||||
|
assertNotMatched(ir1, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchPractitionerPhoneAndEmailParam() {
|
||||||
|
String methodName = "testSearchPractitionerPhoneAndEmailParam";
|
||||||
|
Practitioner patient = new Practitioner();
|
||||||
|
patient.addName().setFamily(methodName);
|
||||||
|
patient.addTelecom().setSystem(ContactPoint.ContactPointSystem.PHONE).setValue("123");
|
||||||
|
|
||||||
|
SearchParameterMap params;
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Practitioner.SP_FAMILY, new StringParam(methodName));
|
||||||
|
params.add(Practitioner.SP_EMAIL, new TokenParam(null, "123"));
|
||||||
|
assertNotMatched(patient, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Practitioner.SP_FAMILY, new StringParam(methodName));
|
||||||
|
assertMatched(patient, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Practitioner.SP_FAMILY, new StringParam(methodName));
|
||||||
|
params.add(Practitioner.SP_EMAIL, new TokenParam(null, "abc"));
|
||||||
|
assertNotMatched(patient, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Practitioner.SP_FAMILY, new StringParam(methodName));
|
||||||
|
params.add(Practitioner.SP_PHONE, new TokenParam(null, "123"));
|
||||||
|
assertMatched(patient, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchQuantityWrongParam() {
|
||||||
|
Condition c1 = new Condition();
|
||||||
|
c1.setAbatement(new Range().setLow((SimpleQuantity) new SimpleQuantity().setValue(1L)).setHigh((SimpleQuantity) new SimpleQuantity().setValue(1L)));
|
||||||
|
SearchParameterMap params = new SearchParameterMap().setLoadSynchronous(true).add(Condition.SP_ABATEMENT_AGE, new QuantityParam("1"));
|
||||||
|
assertMatched(c1, params);
|
||||||
|
|
||||||
|
Condition c2 = new Condition();
|
||||||
|
c2.setOnset(new Range().setLow((SimpleQuantity) new SimpleQuantity().setValue(1L)).setHigh((SimpleQuantity) new SimpleQuantity().setValue(1L)));
|
||||||
|
|
||||||
|
params = new SearchParameterMap().add(Condition.SP_ONSET_AGE, new QuantityParam("1"));
|
||||||
|
assertMatched(c2, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchResourceLinkWithChainUnsupported() {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithChainXX");
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithChain01");
|
||||||
|
IIdType patientId01 = new IdType("Patient", 1L);
|
||||||
|
patient.setId(patientId01);
|
||||||
|
|
||||||
|
Patient patient02 = new Patient();
|
||||||
|
patient02.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithChainXX");
|
||||||
|
patient02.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithChain02");
|
||||||
|
IIdType patientId02 = new IdType("Patient", 2L);
|
||||||
|
patient02.setId(patientId02);
|
||||||
|
|
||||||
|
Observation obs01 = new Observation();
|
||||||
|
obs01.setEffective(new DateTimeType(new Date()));
|
||||||
|
obs01.setSubject(new Reference(patientId01));
|
||||||
|
|
||||||
|
Observation obs02 = new Observation();
|
||||||
|
obs02.setEffective(new DateTimeType(new Date()));
|
||||||
|
obs02.setSubject(new Reference(patientId02));
|
||||||
|
|
||||||
|
SearchParameterMap params = new SearchParameterMap().add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "urn:system|testSearchResourceLinkWithChain01"));
|
||||||
|
assertUnsupported(obs01, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchResourceLinkWithTextLogicalId() {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
String patientName01 = "testSearchResourceLinkWithTextLogicalId01";
|
||||||
|
patient.setId(patientName01);
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithTextLogicalIdXX");
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue(patientName01);
|
||||||
|
IIdType patientId01 = new IdType("Patient", patientName01);
|
||||||
|
|
||||||
|
Patient patient02 = new Patient();
|
||||||
|
String patientName02 = "testSearchResourceLinkWithTextLogicalId02";
|
||||||
|
patient02.setId(patientName02);
|
||||||
|
patient02.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithTextLogicalIdXX");
|
||||||
|
patient02.addIdentifier().setSystem("urn:system").setValue(patientName02);
|
||||||
|
IIdType patientId02 = new IdType("Patient", patientName02);
|
||||||
|
|
||||||
|
Observation obs01 = new Observation();
|
||||||
|
obs01.setEffective(new DateTimeType(new Date()));
|
||||||
|
obs01.setSubject(new Reference(patientId01));
|
||||||
|
|
||||||
|
Observation obs02 = new Observation();
|
||||||
|
obs02.setEffective(new DateTimeType(new Date()));
|
||||||
|
obs02.setSubject(new Reference(patientId02));
|
||||||
|
|
||||||
|
SearchParameterMap params = new SearchParameterMap().add(Observation.SP_SUBJECT, new ReferenceParam(patientName01));
|
||||||
|
assertMatched(obs01, params);
|
||||||
|
assertNotMatched(obs02, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam("testSearchResourceLinkWithTextLogicalId99"));
|
||||||
|
assertNotMatched(obs01, params);
|
||||||
|
assertNotMatched(obs02, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam("999999999999999"));
|
||||||
|
assertNotMatched(obs01, params);
|
||||||
|
assertNotMatched(obs02, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchResourceReferenceOnlyCorrectPath() {
|
||||||
|
Organization org = new Organization();
|
||||||
|
org.setActive(true);
|
||||||
|
IIdType oid1 = new IdType("Organization", 1L);
|
||||||
|
|
||||||
|
Task task = new Task();
|
||||||
|
task.setRequester(new Reference(oid1));
|
||||||
|
Task task2 = new Task();
|
||||||
|
task2.setOwner(new Reference(oid1));
|
||||||
|
|
||||||
|
SearchParameterMap map;
|
||||||
|
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
map.add(Task.SP_REQUESTER, new ReferenceParam(oid1.getValue()));
|
||||||
|
assertMatched(task, map);
|
||||||
|
assertNotMatched(task2, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchStringParam() throws Exception {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
||||||
|
patient.addName().setFamily("Tester_testSearchStringParam").addGiven("Joe");
|
||||||
|
|
||||||
|
SearchParameterMap params;
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("Tester_testSearchStringParam"));
|
||||||
|
assertMatched(patient, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("FOO_testSearchStringParam"));
|
||||||
|
assertNotMatched(patient, params);
|
||||||
|
|
||||||
|
// Try with different casing
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("tester_testsearchstringparam"));
|
||||||
|
assertMatched(patient, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("TESTER_TESTSEARCHSTRINGPARAM"));
|
||||||
|
assertMatched(patient, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchStringParamReallyLong() {
|
||||||
|
String methodName = "testSearchStringParamReallyLong";
|
||||||
|
String value = StringUtils.rightPad(methodName, 200, 'a');
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
||||||
|
patient.addName().setFamily(value);
|
||||||
|
|
||||||
|
SearchParameterMap params;
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
|
||||||
|
String substring = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam(substring));
|
||||||
|
assertMatched(patient, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchStringParamWithNonNormalized() {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
||||||
|
patient.addName().addGiven("testSearchStringParamWithNonNormalized_h\u00F6ra");
|
||||||
|
Patient patient2 = new Patient();
|
||||||
|
patient2.addIdentifier().setSystem("urn:system").setValue("002");
|
||||||
|
patient2.addName().addGiven("testSearchStringParamWithNonNormalized_HORA");
|
||||||
|
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_GIVEN, new StringParam("testSearchStringParamWithNonNormalized_hora"));
|
||||||
|
assertMatched(patient, params);
|
||||||
|
assertMatched(patient2, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchTokenParam() {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam001");
|
||||||
|
patient.addName().setFamily("Tester").addGiven("testSearchTokenParam1");
|
||||||
|
patient.addCommunication().getLanguage().setText("testSearchTokenParamComText").addCoding().setCode("testSearchTokenParamCode").setSystem("testSearchTokenParamSystem")
|
||||||
|
.setDisplay("testSearchTokenParamDisplay");
|
||||||
|
|
||||||
|
Patient patient2 = new Patient();
|
||||||
|
patient2.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam002");
|
||||||
|
patient2.addName().setFamily("Tester").addGiven("testSearchTokenParam2");
|
||||||
|
|
||||||
|
{
|
||||||
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
|
map.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testSearchTokenParam001"));
|
||||||
|
assertMatched(patient, map);
|
||||||
|
assertNotMatched(patient2, map);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
|
map.add(Patient.SP_IDENTIFIER, new TokenParam(null, "testSearchTokenParam001"));
|
||||||
|
assertMatched(patient, map);
|
||||||
|
assertNotMatched(patient2, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
|
map.add(Patient.SP_LANGUAGE, new TokenParam("testSearchTokenParamSystem", "testSearchTokenParamCode"));
|
||||||
|
assertMatched(patient, map);
|
||||||
|
assertNotMatched(patient2, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
|
map.add(Patient.SP_LANGUAGE, new TokenParam(null, "testSearchTokenParamCode", true));
|
||||||
|
assertUnsupported(patient, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
|
TokenOrListParam listParam = new TokenOrListParam();
|
||||||
|
listParam.add("urn:system", "testSearchTokenParam001");
|
||||||
|
listParam.add("urn:system", "testSearchTokenParam002");
|
||||||
|
map.add(Patient.SP_IDENTIFIER, listParam);
|
||||||
|
assertMatched(patient, map);
|
||||||
|
assertMatched(patient2, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
|
TokenOrListParam listParam = new TokenOrListParam();
|
||||||
|
listParam.add(null, "testSearchTokenParam001");
|
||||||
|
listParam.add("urn:system", "testSearchTokenParam002");
|
||||||
|
map.add(Patient.SP_IDENTIFIER, listParam);
|
||||||
|
assertMatched(patient, map);
|
||||||
|
assertMatched(patient2, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchTokenParamNoValue() {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam001");
|
||||||
|
patient.addName().setFamily("Tester").addGiven("testSearchTokenParam1");
|
||||||
|
patient.addCommunication().getLanguage().setText("testSearchTokenParamComText").addCoding().setCode("testSearchTokenParamCode").setSystem("testSearchTokenParamSystem")
|
||||||
|
.setDisplay("testSearchTokenParamDisplay");
|
||||||
|
|
||||||
|
Patient patient2 = new Patient();
|
||||||
|
patient2.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam002");
|
||||||
|
patient2.addName().setFamily("Tester").addGiven("testSearchTokenParam2");
|
||||||
|
|
||||||
|
Patient patient3 = new Patient();
|
||||||
|
patient3.addIdentifier().setSystem("urn:system2").setValue("testSearchTokenParam002");
|
||||||
|
patient3.addName().setFamily("Tester").addGiven("testSearchTokenParam2");
|
||||||
|
|
||||||
|
{
|
||||||
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
|
map.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", null));
|
||||||
|
// Match 2
|
||||||
|
assertMatched(patient, map);
|
||||||
|
assertMatched(patient2, map);
|
||||||
|
assertNotMatched(patient3, map);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
|
map.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", ""));
|
||||||
|
// Match 2
|
||||||
|
assertMatched(patient, map);
|
||||||
|
assertMatched(patient2, map);
|
||||||
|
assertNotMatched(patient3, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchTokenWithNotModifierUnsupported() {
|
||||||
|
String male, female;
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
||||||
|
patient.addName().setFamily("Tester").addGiven("Joe");
|
||||||
|
patient.setGender(Enumerations.AdministrativeGender.MALE);
|
||||||
|
|
||||||
|
List<String> patients;
|
||||||
|
SearchParameterMap params;
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_GENDER, new TokenParam(null, "male"));
|
||||||
|
assertMatched(patient, params);
|
||||||
|
|
||||||
|
params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_GENDER, new TokenParam(null, "male").setModifier(TokenParamModifier.NOT));
|
||||||
|
assertUnsupported(patient, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchTokenWrongParam() {
|
||||||
|
Patient p1 = new Patient();
|
||||||
|
p1.setGender(Enumerations.AdministrativeGender.MALE);
|
||||||
|
|
||||||
|
Patient p2 = new Patient();
|
||||||
|
p2.addIdentifier().setValue(Enumerations.AdministrativeGender.MALE.toCode());
|
||||||
|
|
||||||
|
{
|
||||||
|
SearchParameterMap map = new SearchParameterMap().add(Patient.SP_GENDER, new TokenParam(null, "male"));
|
||||||
|
assertMatched(p1, map);
|
||||||
|
assertNotMatched(p2, map);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true).add(Patient.SP_IDENTIFIER, new TokenParam(null, "male"));
|
||||||
|
assertNotMatched(p1, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchUriWrongParam() {
|
||||||
|
ValueSet v1 = new ValueSet();
|
||||||
|
v1.getUrlElement().setValue("http://foo");
|
||||||
|
|
||||||
|
ValueSet v2 = new ValueSet();
|
||||||
|
v2.getExpansion().getIdentifierElement().setValue("http://foo");
|
||||||
|
|
||||||
|
{
|
||||||
|
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://foo"));
|
||||||
|
assertMatched(v1, map);
|
||||||
|
assertNotMatched(v2, map);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_EXPANSION, new UriParam("http://foo"));
|
||||||
|
assertNotMatched(v1, map);
|
||||||
|
assertMatched(v2, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchValueQuantity() {
|
||||||
|
String methodName = "testSearchValueQuantity";
|
||||||
|
|
||||||
|
Observation o1 = new Observation();
|
||||||
|
o1.getCode().addCoding().setSystem("urn:foo").setCode(methodName + "code");
|
||||||
|
Quantity q1 = new Quantity().setSystem("urn:bar:" + methodName).setCode(methodName + "units").setValue(10);
|
||||||
|
o1.setValue(q1);
|
||||||
|
Observation o2 = new Observation();
|
||||||
|
o2.getCode().addCoding().setSystem("urn:foo").setCode(methodName + "code");
|
||||||
|
Quantity q2 = new Quantity().setSystem("urn:bar:" + methodName).setCode(methodName + "units").setValue(5);
|
||||||
|
o2.setValue(q2);
|
||||||
|
|
||||||
|
SearchParameterMap map;
|
||||||
|
IBundleProvider found;
|
||||||
|
QuantityParam param;
|
||||||
|
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
param = new QuantityParam(null, new BigDecimal("10"), null, null);
|
||||||
|
map.add(Observation.SP_VALUE_QUANTITY, param);
|
||||||
|
assertMatched(o1, map);
|
||||||
|
assertNotMatched(o2, map);
|
||||||
|
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
param = new QuantityParam(null, new BigDecimal("10"), null, methodName + "units");
|
||||||
|
map.add(Observation.SP_VALUE_QUANTITY, param);
|
||||||
|
assertMatched(o1, map);
|
||||||
|
assertNotMatched(o2, map);
|
||||||
|
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
map.setLoadSynchronous(true);
|
||||||
|
param = new QuantityParam(null, new BigDecimal("10"), "urn:bar:" + methodName, null);
|
||||||
|
map.add(Observation.SP_VALUE_QUANTITY, param);
|
||||||
|
assertMatched(o1, map);
|
||||||
|
assertNotMatched(o2, map);
|
||||||
|
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
map.setLoadSynchronous(true);
|
||||||
|
param = new QuantityParam(null, new BigDecimal("10"), "urn:bar:" + methodName, methodName + "units");
|
||||||
|
map.add(Observation.SP_VALUE_QUANTITY, param);
|
||||||
|
assertMatched(o1, map);
|
||||||
|
assertNotMatched(o2, map);
|
||||||
|
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
map.setLoadSynchronous(true);
|
||||||
|
param = new QuantityParam(null, new BigDecimal("1000"), "urn:bar:" + methodName, methodName + "units");
|
||||||
|
map.add(Observation.SP_VALUE_QUANTITY, param);
|
||||||
|
assertNotMatched(o1, map);
|
||||||
|
assertNotMatched(o2, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithContainsUnsupported() {
|
||||||
|
Patient pt1 = new Patient();
|
||||||
|
pt1.addName().setFamily("ABCDEFGHIJK");
|
||||||
|
|
||||||
|
List<String> ids;
|
||||||
|
SearchParameterMap map;
|
||||||
|
IBundleProvider results;
|
||||||
|
|
||||||
|
// Contains = true
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
map.add(Patient.SP_NAME, new StringParam("FGHIJK").setContains(true));
|
||||||
|
assertUnsupported(pt1, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithDate() {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
||||||
|
|
||||||
|
Patient patient2 = new Patient();
|
||||||
|
patient2.addIdentifier().setSystem("urn:system").setValue("002");
|
||||||
|
patient2.addName().setFamily("Tester_testSearchStringParam").addGiven("John");
|
||||||
|
patient2.setBirthDateElement(new DateType("2011-01-01"));
|
||||||
|
{
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.setLoadSynchronous(true);
|
||||||
|
params.add(Patient.SP_BIRTHDATE, new DateParam("2011-01-01"));
|
||||||
|
assertNotMatched(patient, params);
|
||||||
|
assertMatched(patient2, params);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.setLoadSynchronous(true);
|
||||||
|
params.add(Patient.SP_BIRTHDATE, new DateParam("2011-01-03"));
|
||||||
|
assertNotMatched(patient, params);
|
||||||
|
assertNotMatched(patient2, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithIncludesIgnored() {
|
||||||
|
String methodName = "testSearchWithIncludes";
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
||||||
|
patient.addName().setFamily("Tester_" + methodName + "_P1").addGiven("Joe");
|
||||||
|
|
||||||
|
{
|
||||||
|
// No includes
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1"));
|
||||||
|
assertMatched(patient, params);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Named include
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1"));
|
||||||
|
params.addInclude(Patient.INCLUDE_ORGANIZATION.asNonRecursive());
|
||||||
|
assertMatched(patient, params);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Named include with parent non-recursive
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1"));
|
||||||
|
params.addInclude(Patient.INCLUDE_ORGANIZATION);
|
||||||
|
params.addInclude(Organization.INCLUDE_PARTOF.asNonRecursive());
|
||||||
|
assertMatched(patient, params);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Named include with parent recursive
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1"));
|
||||||
|
params.addInclude(Patient.INCLUDE_ORGANIZATION);
|
||||||
|
params.addInclude(Organization.INCLUDE_PARTOF.asRecursive());
|
||||||
|
assertMatched(patient, params);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// * include non recursive
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1"));
|
||||||
|
params.addInclude(IBaseResource.INCLUDE_ALL.asNonRecursive());
|
||||||
|
assertMatched(patient, params);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// * include recursive
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1"));
|
||||||
|
params.addInclude(IBaseResource.INCLUDE_ALL.asRecursive());
|
||||||
|
assertMatched(patient, params);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Irrelevant include
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add(Patient.SP_FAMILY, new StringParam("Tester_" + methodName + "_P1"));
|
||||||
|
params.addInclude(Encounter.INCLUDE_EPISODE_OF_CARE);
|
||||||
|
assertMatched(patient, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithSecurityAndProfileParamsUnsupported() {
|
||||||
|
String methodName = "testSearchWithSecurityAndProfileParams";
|
||||||
|
|
||||||
|
Organization org = new Organization();
|
||||||
|
org.getNameElement().setValue("FOO");
|
||||||
|
org.getMeta().addSecurity("urn:taglist", methodName + "1a", null);
|
||||||
|
{
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add("_security", new TokenParam("urn:taglist", methodName + "1a"));
|
||||||
|
assertUnsupported(org, params);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add("_profile", new UriParam("http://" + methodName));
|
||||||
|
assertUnsupported(org, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithTagParameterUnsupported() {
|
||||||
|
String methodName = "testSearchWithTagParameter";
|
||||||
|
|
||||||
|
Organization org = new Organization();
|
||||||
|
org.getNameElement().setValue("FOO");
|
||||||
|
org.getMeta().addTag("urn:taglist", methodName + "1a", null);
|
||||||
|
org.getMeta().addTag("urn:taglist", methodName + "1b", null);
|
||||||
|
|
||||||
|
{
|
||||||
|
// One tag
|
||||||
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
|
params.add("_tag", new TokenParam("urn:taglist", methodName + "1a"));
|
||||||
|
assertUnsupported(org, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithVeryLongUrlLonger() {
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.addName().setFamily("A1");
|
||||||
|
|
||||||
|
|
||||||
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
|
StringOrListParam or = new StringOrListParam();
|
||||||
|
or.addOr(new StringParam("A1"));
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
or.addOr(new StringParam(StringUtils.leftPad("", 200, (char) ('A' + i))));
|
||||||
|
}
|
||||||
|
map.add(Patient.SP_NAME, or);
|
||||||
|
assertMatched(p, map);
|
||||||
|
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
or = new StringOrListParam();
|
||||||
|
or.addOr(new StringParam("A1"));
|
||||||
|
or.addOr(new StringParam("A1"));
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
or.addOr(new StringParam(StringUtils.leftPad("", 200, (char) ('A' + i))));
|
||||||
|
}
|
||||||
|
map.add(Patient.SP_NAME, or);
|
||||||
|
assertMatched(p, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDateSearchParametersShouldBeTimezoneIndependent() {
|
||||||
|
|
||||||
|
List<Observation> nlist = new ArrayList<>();
|
||||||
|
nlist.add(createObservationWithEffective("NO1", "2011-01-02T23:00:00-11:30"));
|
||||||
|
nlist.add(createObservationWithEffective("NO2", "2011-01-03T00:00:00+01:00"));
|
||||||
|
|
||||||
|
List<Observation> ylist = new ArrayList<>();
|
||||||
|
ylist.add(createObservationWithEffective("YES01", "2011-01-02T00:00:00-11:30"));
|
||||||
|
ylist.add(createObservationWithEffective("YES02", "2011-01-02T00:00:00-10:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES03", "2011-01-02T00:00:00-09:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES04", "2011-01-02T00:00:00-08:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES05", "2011-01-02T00:00:00-07:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES06", "2011-01-02T00:00:00-06:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES07", "2011-01-02T00:00:00-05:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES08", "2011-01-02T00:00:00-04:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES09", "2011-01-02T00:00:00-03:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES10", "2011-01-02T00:00:00-02:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES11", "2011-01-02T00:00:00-01:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES12", "2011-01-02T00:00:00Z"));
|
||||||
|
ylist.add(createObservationWithEffective("YES13", "2011-01-02T00:00:00+01:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES14", "2011-01-02T00:00:00+02:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES15", "2011-01-02T00:00:00+03:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES16", "2011-01-02T00:00:00+04:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES17", "2011-01-02T00:00:00+05:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES18", "2011-01-02T00:00:00+06:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES19", "2011-01-02T00:00:00+07:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES20", "2011-01-02T00:00:00+08:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES21", "2011-01-02T00:00:00+09:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES22", "2011-01-02T00:00:00+10:00"));
|
||||||
|
ylist.add(createObservationWithEffective("YES23", "2011-01-02T00:00:00+11:00"));
|
||||||
|
|
||||||
|
|
||||||
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
|
map.add(Observation.SP_DATE, new DateParam("2011-01-02"));
|
||||||
|
|
||||||
|
for (Observation obs : nlist) {
|
||||||
|
// assertNotMatched(obs, map);
|
||||||
|
}
|
||||||
|
for (Observation obs : ylist) {
|
||||||
|
ourLog.info("Obs {} has time {}", obs.getId(), obs.getEffectiveDateTimeType().getValue().toString());
|
||||||
|
assertMatched(obs, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Observation createObservationWithEffective(String theId, String theEffective) {
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.setId(theId);
|
||||||
|
obs.setEffective(new DateTimeType(theEffective));
|
||||||
|
return obs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithVeryLongUrlShorter() {
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.addName().setFamily("A1");
|
||||||
|
|
||||||
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
|
StringOrListParam or = new StringOrListParam();
|
||||||
|
or.addOr(new StringParam("A1"));
|
||||||
|
or.addOr(new StringParam(StringUtils.leftPad("", 200, 'A')));
|
||||||
|
or.addOr(new StringParam(StringUtils.leftPad("", 200, 'B')));
|
||||||
|
or.addOr(new StringParam(StringUtils.leftPad("", 200, 'C')));
|
||||||
|
map.add(Patient.SP_NAME, or);
|
||||||
|
|
||||||
|
assertMatched(p, map);
|
||||||
|
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
or = new StringOrListParam();
|
||||||
|
or.addOr(new StringParam("A1"));
|
||||||
|
or.addOr(new StringParam(StringUtils.leftPad("", 200, 'A')));
|
||||||
|
or.addOr(new StringParam(StringUtils.leftPad("", 200, 'B')));
|
||||||
|
or.addOr(new StringParam(StringUtils.leftPad("", 200, 'C')));
|
||||||
|
map.add(Patient.SP_NAME, or);
|
||||||
|
assertMatched(p, map);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,9 +13,12 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import ca.uhn.fhir.util.BundleUtil;
|
import ca.uhn.fhir.util.BundleUtil;
|
||||||
import ca.uhn.fhir.util.PortUtil;
|
import ca.uhn.fhir.util.PortUtil;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import net.ttddyy.dsproxy.QueryCount;
|
||||||
|
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
@ -23,8 +26,10 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.*;
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -33,8 +38,7 @@ import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.hasItem;
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the rest-hook subscriptions
|
* Test the rest-hook subscriptions
|
||||||
|
@ -50,9 +54,35 @@ public class RestHookTestR4Test extends BaseResourceProviderR4Test {
|
||||||
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
|
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
|
||||||
private static List<String> ourContentTypes = Collections.synchronizedList(new ArrayList<>());
|
private static List<String> ourContentTypes = Collections.synchronizedList(new ArrayList<>());
|
||||||
private static List<String> ourHeaders = Collections.synchronizedList(new ArrayList<>());
|
private static List<String> ourHeaders = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
private static SingleQueryCountHolder ourCountHolder;
|
||||||
private List<IIdType> mySubscriptionIds = Collections.synchronizedList(new ArrayList<>());
|
private List<IIdType> mySubscriptionIds = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SingleQueryCountHolder myCountHolder;
|
||||||
|
@Autowired
|
||||||
|
private DaoConfig myDaoConfig;
|
||||||
|
|
||||||
private CountingInterceptor myCountingInterceptor;
|
private CountingInterceptor myCountingInterceptor;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void initializeOurCountHolder() {
|
||||||
|
ourCountHolder = myCountHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void enableInMemory() {
|
||||||
|
myDaoConfig.setEnableInMemorySubscriptionMatching(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void reportTotalSelects() {
|
||||||
|
ourLog.info("Total database select queries: {}", getQueryCount().getSelect());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static QueryCount getQueryCount() {
|
||||||
|
return ourCountHolder.getQueryCountMap().get("");
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void afterUnregisterRestHookListener() {
|
public void afterUnregisterRestHookListener() {
|
||||||
for (IIdType next : mySubscriptionIds) {
|
for (IIdType next : mySubscriptionIds) {
|
||||||
|
@ -98,6 +128,16 @@ public class RestHookTestR4Test extends BaseResourceProviderR4Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Subscription createSubscription(String theCriteria, String thePayload, String theEndpoint) throws InterruptedException {
|
private Subscription createSubscription(String theCriteria, String thePayload, String theEndpoint) throws InterruptedException {
|
||||||
|
Subscription subscription = newSubscription(theCriteria, thePayload, theEndpoint);
|
||||||
|
|
||||||
|
MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute();
|
||||||
|
subscription.setId(methodOutcome.getId().getIdPart());
|
||||||
|
mySubscriptionIds.add(methodOutcome.getId());
|
||||||
|
|
||||||
|
return subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Subscription newSubscription(String theCriteria, String thePayload, String theEndpoint) {
|
||||||
Subscription subscription = new Subscription();
|
Subscription subscription = new Subscription();
|
||||||
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
|
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
|
||||||
subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED);
|
subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED);
|
||||||
|
@ -107,11 +147,6 @@ public class RestHookTestR4Test extends BaseResourceProviderR4Test {
|
||||||
channel.setType(Subscription.SubscriptionChannelType.RESTHOOK);
|
channel.setType(Subscription.SubscriptionChannelType.RESTHOOK);
|
||||||
channel.setPayload(thePayload);
|
channel.setPayload(thePayload);
|
||||||
channel.setEndpoint(theEndpoint);
|
channel.setEndpoint(theEndpoint);
|
||||||
|
|
||||||
MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute();
|
|
||||||
subscription.setId(methodOutcome.getId().getIdPart());
|
|
||||||
mySubscriptionIds.add(methodOutcome.getId());
|
|
||||||
|
|
||||||
return subscription;
|
return subscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,9 +343,9 @@ public class RestHookTestR4Test extends BaseResourceProviderR4Test {
|
||||||
waitForSize(0, ourCreatedObservations);
|
waitForSize(0, ourCreatedObservations);
|
||||||
waitForSize(5, ourUpdatedObservations);
|
waitForSize(5, ourUpdatedObservations);
|
||||||
|
|
||||||
Assert.assertFalse(subscription1.getId().equals(subscription2.getId()));
|
assertFalse(subscription1.getId().equals(subscription2.getId()));
|
||||||
Assert.assertFalse(observation1.getId().isEmpty());
|
assertFalse(observation1.getId().isEmpty());
|
||||||
Assert.assertFalse(observation2.getId().isEmpty());
|
assertFalse(observation2.getId().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -382,9 +417,9 @@ public class RestHookTestR4Test extends BaseResourceProviderR4Test {
|
||||||
waitForSize(0, ourCreatedObservations);
|
waitForSize(0, ourCreatedObservations);
|
||||||
waitForSize(5, ourUpdatedObservations);
|
waitForSize(5, ourUpdatedObservations);
|
||||||
|
|
||||||
Assert.assertFalse(subscription1.getId().equals(subscription2.getId()));
|
assertFalse(subscription1.getId().equals(subscription2.getId()));
|
||||||
Assert.assertFalse(observation1.getId().isEmpty());
|
assertFalse(observation1.getId().isEmpty());
|
||||||
Assert.assertFalse(observation2.getId().isEmpty());
|
assertFalse(observation2.getId().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -533,6 +568,30 @@ public class RestHookTestR4Test extends BaseResourceProviderR4Test {
|
||||||
RestHookTestDstu2Test.waitForQueueToDrain(getRestHookSubscriptionInterceptor());
|
RestHookTestDstu2Test.waitForQueueToDrain(getRestHookSubscriptionInterceptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected= UnprocessableEntityException.class)
|
||||||
|
public void testInvalidProvenanceParam() {
|
||||||
|
String payload = "application/fhir+json";
|
||||||
|
String criteriabad = "Provenance?activity=http://hl7.org/fhir/v3/DocumentCompletion%7CAU";
|
||||||
|
Subscription subscription = newSubscription(criteriabad, payload, ourListenerServerBase);
|
||||||
|
ourClient.create().resource(subscription).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected= UnprocessableEntityException.class)
|
||||||
|
public void testInvalidProcedureRequestParam() {
|
||||||
|
String payload = "application/fhir+json";
|
||||||
|
String criteriabad = "ProcedureRequest?intent=instance-order&category=Laboratory";
|
||||||
|
Subscription subscription = newSubscription(criteriabad, payload, ourListenerServerBase);
|
||||||
|
ourClient.create().resource(subscription).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected= UnprocessableEntityException.class)
|
||||||
|
public void testInvalidBodySiteParam() {
|
||||||
|
String payload = "application/fhir+json";
|
||||||
|
String criteriabad = "BodySite?accessType=Catheter";
|
||||||
|
Subscription subscription = newSubscription(criteriabad, payload, ourListenerServerBase);
|
||||||
|
ourClient.create().resource(subscription).execute();
|
||||||
|
}
|
||||||
|
|
||||||
public static class ObservationListener implements IResourceProvider {
|
public static class ObservationListener implements IResourceProvider {
|
||||||
|
|
||||||
@Create
|
@Create
|
||||||
|
|
|
@ -159,12 +159,6 @@ public class JpaServerDemo extends RestfulServer {
|
||||||
if (fhirVersion == FhirVersionEnum.DSTU3) {
|
if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||||
registerProvider(myAppCtx.getBean(TerminologyUploaderProviderDstu3.class));
|
registerProvider(myAppCtx.getBean(TerminologyUploaderProviderDstu3.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable various subscription types
|
|
||||||
registerInterceptor(myAppCtx.getBean(SubscriptionWebsocketInterceptor.class));
|
|
||||||
registerInterceptor(myAppCtx.getBean(SubscriptionRestHookInterceptor.class));
|
|
||||||
registerInterceptor(myAppCtx.getBean(SubscriptionEmailInterceptor.class));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||||
import ca.uhn.fhir.spring.boot.autoconfigure.FhirAutoConfiguration.FhirJpaServerConfiguration.Dstu3;
|
import ca.uhn.fhir.spring.boot.autoconfigure.FhirAutoConfiguration.FhirJpaServerConfiguration.Dstu3;
|
||||||
import org.assertj.core.util.Arrays;
|
import org.assertj.core.util.Arrays;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
|
@ -55,10 +56,19 @@ public class FhirAutoConfigurationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void withFhirVersion() throws Exception {
|
public void withFhirVersion() throws Exception {
|
||||||
load("hapi.fhir.version:DSTU3");
|
load(Arrays.array(EmbeddedDataSourceConfiguration.class,
|
||||||
|
HibernateJpaAutoConfiguration.class,
|
||||||
|
FhirAutoConfiguration.class),
|
||||||
|
"hapi.fhir.version:DSTU3", "spring.jpa.properties.hibernate.search.default.indexBase:target/lucenefiles",
|
||||||
|
"spring.jpa.properties.hibernate.search.model_mapping:ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory");
|
||||||
assertThat(this.context.getBean(FhirContext.class).getVersion()).isEqualTo(FhirVersionEnum.DSTU3.getVersionImplementation());
|
assertThat(this.context.getBean(FhirContext.class).getVersion()).isEqualTo(FhirVersionEnum.DSTU3.getVersionImplementation());
|
||||||
|
|
||||||
load("hapi.fhir.version:R4");
|
load(Arrays.array(EmbeddedDataSourceConfiguration.class,
|
||||||
|
HibernateJpaAutoConfiguration.class,
|
||||||
|
FhirAutoConfiguration.class),
|
||||||
|
"hapi.fhir.version:R4",
|
||||||
|
"spring.jpa.properties.hibernate.search.default.indexBase:target/lucenefiles",
|
||||||
|
"spring.jpa.properties.hibernate.search.model_mapping:ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory");
|
||||||
assertThat(this.context.getBean(FhirContext.class).getVersion()).isEqualTo(FhirVersionEnum.R4.getVersionImplementation());
|
assertThat(this.context.getBean(FhirContext.class).getVersion()).isEqualTo(FhirVersionEnum.R4.getVersionImplementation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||||
import org.hl7.fhir.dstu3.model.Patient;
|
import org.hl7.fhir.dstu3.model.Patient;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,20 @@
|
||||||
</ul>
|
</ul>
|
||||||
]]>
|
]]>
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
Changed subscription processing, if the subscription criteria are straightforward (i.e. no
|
||||||
|
chained references, qualifiers or prefixes) then attempt to match the incoming resource against
|
||||||
|
the criteria in-memory. If the subscription criteria can't be matched in-memory, then the
|
||||||
|
server falls back to the original subscription matching process of querying the database. The
|
||||||
|
in-memory matcher can be disabled by setting isEnableInMemorySubscriptionMatching to "false" in
|
||||||
|
DaoConfig (by default it is true). If isEnableInMemorySubscriptionMatching is "false", then all
|
||||||
|
subscription matching will query the database as before.
|
||||||
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
Changed behaviour of FHIR Server to reject subscriptions with invalid criteria. If a Subscription
|
||||||
|
is submitted with invalid criteria, the server returns HTTP 422 "Unprocessable Entity" and the
|
||||||
|
Subscription is not persisted.
|
||||||
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
The JPA server $expunge operation could sometimes fail to expunge if
|
The JPA server $expunge operation could sometimes fail to expunge if
|
||||||
another resource linked to a resource that was being
|
another resource linked to a resource that was being
|
||||||
|
@ -57,6 +71,16 @@
|
||||||
used to return a raw response. This is handy for invoking operations
|
used to return a raw response. This is handy for invoking operations
|
||||||
that might return arbitrary binary content.
|
that might return arbitrary binary content.
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
Moved state and functionality out of BaseHapiFhirDao.java into new classes: LogicalReferenceHelper,
|
||||||
|
ResourceIndexedSearchParams, IdHelperService, SearcchParamExtractorService, and MatchUrlService.
|
||||||
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
Replaced explicit @Bean construction in BaseConfig.java with @ComponentScan. Beans with state are annotated with
|
||||||
|
@Component and stateless beans are annotated as @Service. Also changed SearchBuilder.java and the
|
||||||
|
three Subscriber classes into @Scope("protoype") so their dependencies can be @Autowired injected
|
||||||
|
as opposed to constructor parameters.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="3.6.0" date="2018-11-12" description="Food">
|
<release version="3.6.0" date="2018-11-12" description="Food">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
|
Loading…
Reference in New Issue