Merge remote-tracking branch 'upstream/master'

This commit is contained in:
bdenton 2019-01-30 12:09:25 -08:00
commit ca3b17f3b9
172 changed files with 7984 additions and 2664 deletions

110
.mvn/wrapper/MavenWrapperDownloader.java vendored Executable file
View File

@ -0,0 +1,110 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL =
"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: : " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

1
.mvn/wrapper/maven-wrapper.properties vendored Executable file
View File

@ -0,0 +1 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip

View File

@ -92,6 +92,11 @@
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-base</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>

View File

@ -1,15 +1,11 @@
package example;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.List;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
@ -17,6 +13,12 @@ import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.auth.*;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@SuppressWarnings("unused")
public class AuthorizationInterceptors {
@ -158,4 +160,47 @@ public class AuthorizationInterceptors {
//END SNIPPET: patchAll
}
//START SNIPPET: narrowing
public class MyPatientSearchNarrowingInterceptor extends SearchNarrowingInterceptor {
/**
* This method must be overridden to provide the list of compartments
* and/or resources that the current user should have access to
*/
@Override
protected AuthorizedList buildAuthorizedList(RequestDetails theRequestDetails) {
// Process authorization header - The following is a fake
// implementation. Obviously we'd want something more real
// for a production scenario.
//
// In this basic example we have two hardcoded bearer tokens,
// one which is for a user that has access to one patient, and
// another that has full access.
String authHeader = theRequestDetails.getHeader("Authorization");
if ("Bearer dfw98h38r".equals(authHeader)) {
// This user will have access to two compartments
return new AuthorizedList()
.addCompartment("Patient/123")
.addCompartment("Patient/456");
} else if ("Bearer 39ff939jgg".equals(authHeader)) {
// This user has access to everything
return new AuthorizedList();
} else {
throw new AuthenticationException("Unknown bearer token");
}
}
}
//END SNIPPET: narrowing
}

View File

@ -0,0 +1,25 @@
package example.interceptor;
import ca.uhn.fhir.jpa.model.interceptor.api.Hook;
import ca.uhn.fhir.jpa.model.interceptor.api.Interceptor;
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage;
/**
* Interceptor class
*/
@Interceptor
public class MyTestInterceptor {
@Hook(Pointcut.SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY)
public boolean beforeRestHookDelivery(ResourceDeliveryMessage theDeliveryMessage, CanonicalSubscription theSubscription) {
String header = "Authorization: Bearer 1234567";
theSubscription.addHeader(header);
return true;
}
}

View File

@ -162,12 +162,45 @@
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<inherited>true</inherited>
<executions>
<execution>
<id>standard</id>
<phase>validate</phase>
<goals>
<goal>create</goal>
</goals>
</execution>
<execution>
<id>downstream</id>
<phase>validate</phase>
<goals>
<goal>create-metadata</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/generated-sources/properties</outputDirectory>
<outputName>ca/uhn/fhir/hapi-fhir-base-build.properties</outputName>
<revisionPropertyName>hapifhir.buildnumber</revisionPropertyName>
<timestampPropertyName>hapifhir.timestamp</timestampPropertyName>
<timestampFormat>yyyy-MM-dd'T'HH:mm:ss.SXXX</timestampFormat>
<versionPropertyName>hapifhir.version</versionPropertyName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>${project.build.directory}/generated-sources/properties</directory>
<filtering>false</filtering>
</resource>
</resources>
</build>

View File

@ -1,8 +1,10 @@
package ca.uhn.fhir.rest.gclient;
import ca.uhn.fhir.context.FhirContext;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IIdType;
import java.util.Arrays;
import java.util.Collection;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -97,7 +99,19 @@ public class ReferenceClientParam extends BaseClientParam implements IParam {
public ICriterion<ReferenceClientParam> hasAnyOfIds(Collection<String> theIds) {
return new StringCriterion<>(getParamName(), theIds);
}
/**
* Match the referenced resource if the resource has ANY of the given IDs
* (this is an OR search, not an AND search), (this can be the logical ID or
* the absolute URL of the resource). Note that to specify an AND search,
* simply add a subsequent {@link IQuery#where(ICriterion) where} criteria
* with the same parameter.
*/
public ICriterion<ReferenceClientParam> hasAnyOfIds(String... theIds) {
Validate.notNull(theIds, "theIds must not be null");
return hasAnyOfIds(Arrays.asList(theIds));
}
private static class ReferenceChainCriterion implements ICriterion<ReferenceClientParam>, ICriterionInternal {
private final String myResourceTypeQualifier;

View File

@ -62,5 +62,10 @@ public abstract class BaseAndListParam<T extends IQueryParameterOr<?>> implement
return myValues.toString();
}
/**
* Returns the number of AND parameters
*/
public int size() {
return myValues.size();
}
}

View File

@ -255,7 +255,15 @@ public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQuer
return b.build();
}
public class DateParamDateTimeHolder extends BaseDateTimeDt {
public static class DateParamDateTimeHolder extends BaseDateTimeDt {
/**
* Constructor
*/
public DateParamDateTimeHolder() {
super();
}
@Override
protected TemporalPrecisionEnum getDefaultPrecisionForDatatype() {
return TemporalPrecisionEnum.SECOND;

View File

@ -20,8 +20,10 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/*
* #%L
@ -208,6 +210,13 @@ public class ParameterUtil {
|| IPrimitiveType.class.isAssignableFrom(theClass);
}
public static String escapeAndJoinOrList(Collection<String> theValues) {
return theValues
.stream()
.map(ParameterUtil::escape)
.collect(Collectors.joining(","));
}
public static int nonEscapedIndexOf(String theString, char theCharacter) {
for (int i = 0; i < theString.length(); i++) {
if (theString.charAt(i) == theCharacter) {

View File

@ -1,6 +1,9 @@
package ca.uhn.fhir.util;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import com.google.common.escape.Escaper;
@ -172,8 +175,13 @@ public class UrlUtil {
return true;
}
public static void main(String[] args) {
System.out.println(escapeUrlParam("http://snomed.info/sct?fhir_vs=isa/126851005"));
public static RuntimeResourceDefinition parseUrlResourceType(FhirContext theCtx, String theUrl) throws DataFormatException {
int paramIndex = theUrl.indexOf('?');
String resourceName = theUrl.substring(0, paramIndex);
if (resourceName.contains("/")) {
resourceName = resourceName.substring(resourceName.lastIndexOf('/') + 1);
}
return theCtx.getResourceDefinition(resourceName);
}
public static Map<String, String[]> parseQueryString(String theQueryString) {

View File

@ -20,9 +20,13 @@ package ca.uhn.fhir.util;
* #L%
*/
import org.apache.commons.lang3.StringUtils;
import java.io.InputStream;
import java.util.Properties;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
/**
* Used internally by HAPI to log the version of the HAPI FHIR framework
* once, when the framework is first loaded by the classloader.
@ -31,24 +35,46 @@ public class VersionUtil {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(VersionUtil.class);
private static String ourVersion;
private static String ourBuildNumber;
private static String ourBuildTime;
static {
initialize();
}
public static String getBuildNumber() {
return ourBuildNumber;
}
public static String getBuildTime() {
return ourBuildTime;
}
public static String getVersion() {
return ourVersion;
}
private static void initialize() {
try (InputStream is = VersionUtil.class.getResourceAsStream("/ca/uhn/fhir/hapi-version.properties")) {
try (InputStream is = VersionUtil.class.getResourceAsStream("/ca/uhn/fhir/hapi-fhir-base-build.properties")) {
Properties p = new Properties();
p.load(is);
ourVersion = p.getProperty("version");
ourLog.info("HAPI FHIR version is: " + ourVersion);
if (is != null) {
p.load(is);
}
ourVersion = p.getProperty("hapifhir.version");
ourVersion = defaultIfBlank(ourVersion, "(unknown)");
ourBuildNumber = p.getProperty("hapifhir.buildnumber");
ourBuildTime = p.getProperty("hapifhir.timestamp");
if (System.getProperty("suppress_hapi_fhir_version_log") == null) {
ourLog.info("HAPI FHIR version {} - Rev {}", ourVersion, StringUtils.right(ourBuildNumber, 10));
}
} catch (Exception e) {
ourLog.warn("Unable to determine HAPI version information", e);
}
}
}

View File

@ -29,14 +29,14 @@ public interface IAnyResource extends IBaseResource {
* Search parameter constant for <b>_language</b>
*/
@SearchParamDefinition(name="_language", path="", description="The language of the resource", type="string" )
public static final String SP_RES_LANGUAGE = "_language";
String SP_RES_LANGUAGE = "_language";
/**
* Search parameter constant for <b>_id</b>
*/
@SearchParamDefinition(name="_id", path="", description="The ID of the resource", type="token" )
public static final String SP_RES_ID = "_id";
String SP_RES_ID = "_id";
/**
* <b>Fluent Client</b> search parameter constant for <b>_id</b>
@ -46,7 +46,7 @@ public interface IAnyResource extends IBaseResource {
* Path: <b>Resource._id</b><br>
* </p>
*/
public static final TokenClientParam RES_ID = new TokenClientParam(IAnyResource.SP_RES_ID);
TokenClientParam RES_ID = new TokenClientParam(IAnyResource.SP_RES_ID);
String getId();
@ -55,11 +55,11 @@ public interface IAnyResource extends IBaseResource {
IPrimitiveType<String> getLanguageElement();
public Object getUserData(String name);
Object getUserData(String name);
@Override
IAnyResource setId(String theId);
public void setUserData(String name, Object value);
void setUserData(String name, Object value);
}

View File

@ -1 +0,0 @@
version=${project.version}

View File

@ -0,0 +1,19 @@
package ca.uhn.fhir.util;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.blankOrNullString;
import static org.junit.Assert.*;
public class VersionUtilTest {
@Test
public void testProperties() {
assertThat(VersionUtil.getVersion(), not(blankOrNullString()));
assertThat(VersionUtil.getBuildNumber(), not(blankOrNullString()));
assertThat(VersionUtil.getBuildTime(), not(blankOrNullString()));
}
}

View File

@ -301,7 +301,7 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
conformance = (IBaseResource) client.fetchConformance().ofType(implementingClass).execute();
} catch (FhirClientConnectionException e) {
if (!myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3) && e.getCause() instanceof DataFormatException) {
capabilityStatementResourceName = "Conformance";
capabilityStatementResourceName = "CapabilityStatement";
implementingClass = myContext.getResourceDefinition(capabilityStatementResourceName).getImplementingClass();
conformance = (IBaseResource) client.fetchConformance().ofType(implementingClass).execute();
} else {

View File

@ -81,7 +81,7 @@ public abstract class BaseQueryParameter implements IParameter {
String paramName = isNotBlank(qualifier) ? getName() + qualifier : getName();
List<String> paramValues = theTargetQueryArguments.get(paramName);
if (paramValues == null) {
paramValues = new ArrayList<String>(value.size());
paramValues = new ArrayList<>(value.size());
theTargetQueryArguments.put(paramName, paramValues);
}

View File

@ -57,10 +57,12 @@ import javax.annotation.Nonnull;
@Configuration
@EnableScheduling
@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),
@ComponentScan.Filter(type=FilterType.REGEX, pattern="ca.uhn.fhir.jpa.subscription.module.standalone.*")})
@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),
@ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*\\.test\\..*"),
@ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Test.*"),
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "ca.uhn.fhir.jpa.subscription.module.standalone.*")})
public abstract class BaseConfig implements SchedulingConfigurer {

View File

@ -7,6 +7,9 @@ import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.model.interceptor.api.HookParams;
import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
@ -58,7 +61,6 @@ 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.hl7.fhir.r4.model.InstantType;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
@ -70,6 +72,8 @@ import org.springframework.data.domain.SliceImpl;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.persistence.*;
@ -116,7 +120,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
public static final String OO_SEVERITY_INFO = "information";
public static final String OO_SEVERITY_WARN = "warning";
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<>();
private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
private static boolean ourValidationDisabledForUnitTest;
private static boolean ourDisableIncrementOnUpdateForUnitTest = false;
@ -126,6 +130,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
@Autowired
protected IdHelperService myIdHelperService;
@Autowired
protected IInterceptorBroadcaster myInterceptorBroadcaster;
@Autowired
protected IForcedIdDao myForcedIdDao;
@Autowired
protected ISearchResultDao mySearchResultDao;
@ -1421,9 +1427,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
return updateEntity(theRequest, theResource, entity, theDeletedTimestampOrNull, true, true, theUpdateTime, false, true);
}
public ResourceTable updateInternal(RequestDetails theRequest, T theResource, boolean thePerformIndexing,
boolean theForceUpdateVersion, RequestDetails theRequestDetails, ResourceTable theEntity, IIdType
theResourceId, IBaseResource theOldResource) {
public ResourceTable updateInternal(RequestDetails theRequestDetails, T theResource, boolean thePerformIndexing, boolean theForceUpdateVersion,
ResourceTable theEntity, IIdType theResourceId, IBaseResource theOldResource) {
// Notify interceptors
ActionRequestDetails requestDetails;
if (theRequestDetails != null) {
@ -1440,9 +1445,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
((IServerOperationInterceptor) next).resourcePreUpdate(theRequestDetails, theOldResource, theResource);
}
}
HookParams hookParams = new HookParams()
.add(IBaseResource.class, theOldResource)
.add(IBaseResource.class, theResource);
myInterceptorBroadcaster.callHooks(Pointcut.OP_PRESTORAGE_RESOURCE_UPDATED, hookParams);
// Perform update
ResourceTable savedEntity = updateEntity(theRequest, theResource, theEntity, null, thePerformIndexing, thePerformIndexing, new Date(), theForceUpdateVersion, thePerformIndexing);
ResourceTable savedEntity = updateEntity(theRequestDetails, theResource, theEntity, null, thePerformIndexing, thePerformIndexing, new Date(), theForceUpdateVersion, thePerformIndexing);
/*
* If we aren't indexing (meaning we're probably executing a sub-operation within a transaction),
@ -1471,7 +1480,17 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
((IServerOperationInterceptor) next).resourceUpdated(theRequestDetails, theOldResource, theResource);
}
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void beforeCommit(boolean readOnly) {
HookParams hookParams = new HookParams()
.add(IBaseResource.class, theOldResource)
.add(IBaseResource.class, theResource);
myInterceptorBroadcaster.callHooks(Pointcut.OP_PRECOMMIT_RESOURCE_UPDATED, hookParams);
}
});
}
return savedEntity;
}
@ -1481,6 +1500,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
id = getContext().getVersion().newIdType().setValue(id.getValue());
}
if (id.hasResourceType() == false) {
id = id.withResourceType(theEntity.getResourceType());
}
theResource.setId(id);
if (theResource instanceof IResource) {
ResourceMetadataKeyEnum.VERSION.put((IResource) theResource, id.getVersionIdPart());

View File

@ -26,6 +26,8 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.r4.MatchResourceUrlService;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.model.interceptor.api.HookParams;
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
@ -35,7 +37,9 @@ import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.jpa.util.ExpungeOutcome;
import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
@ -57,10 +61,11 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
@ -172,7 +177,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
@Override
public DaoMethodOutcome delete(IIdType theId, List<DeleteConflict> theDeleteConflicts, RequestDetails theReques) {
public DaoMethodOutcome delete(IIdType theId, List<DeleteConflict> theDeleteConflicts, RequestDetails theRequest) {
if (theId == null || !theId.hasIdPart()) {
throw new InvalidRequestException("Can not perform delete, no ID provided");
}
@ -205,12 +210,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
T resourceToDelete = toResource(myResourceType, entity, null, false);
// Notify IServerOperationInterceptors about pre-action call
if (theReques != null) {
theReques.getRequestOperationCallback().resourcePreDelete(resourceToDelete);
if (theRequest != null) {
theRequest.getRequestOperationCallback().resourcePreDelete(resourceToDelete);
}
for (IServerInterceptor next : getConfig().getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourcePreDelete(theReques, resourceToDelete);
((IServerOperationInterceptor) next).resourcePreDelete(theRequest, resourceToDelete);
}
}
@ -219,25 +224,33 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
preDelete(resourceToDelete, entity);
// Notify interceptors
if (theReques != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theReques, getContext(), theId.getResourceType(), theId);
if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, getContext(), theId.getResourceType(), theId);
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
}
Date updateTime = new Date();
ResourceTable savedEntity = updateEntity(theReques, null, entity, updateTime, updateTime);
ResourceTable savedEntity = updateEntity(theRequest, null, entity, updateTime, updateTime);
resourceToDelete.setId(entity.getIdDt());
// Notify JPA interceptors
if (theReques != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theReques, getContext(), theId.getResourceType(), theId);
theReques.getRequestOperationCallback().resourceDeleted(resourceToDelete);
if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, getContext(), theId.getResourceType(), theId);
theRequest.getRequestOperationCallback().resourceDeleted(resourceToDelete);
}
for (IServerInterceptor next : getConfig().getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourceDeleted(theReques, resourceToDelete);
((IServerOperationInterceptor) next).resourceDeleted(theRequest, resourceToDelete);
}
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void beforeCommit(boolean readOnly) {
HookParams hookParams = new HookParams()
.add(IBaseResource.class, resourceToDelete);
myInterceptorBroadcaster.callHooks(Pointcut.OP_PRECOMMIT_RESOURCE_DELETED, hookParams);
}
});
DaoMethodOutcome outcome = toMethodOutcome(savedEntity, resourceToDelete).setCreated(true);
@ -320,6 +333,14 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
((IServerOperationInterceptor) next).resourceDeleted(theRequest, resourceToDelete);
}
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void beforeCommit(boolean readOnly) {
HookParams hookParams = new HookParams()
.add(IBaseResource.class, resourceToDelete);
myInterceptorBroadcaster.callHooks(Pointcut.OP_PRECOMMIT_RESOURCE_DELETED, hookParams);
}
});
}
IBaseOperationOutcome oo;
@ -422,6 +443,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
((IServerOperationInterceptor) next).resourcePreCreate(theRequest, theResource);
}
}
HookParams hookParams = new HookParams()
.add(IBaseResource.class, theResource);
myInterceptorBroadcaster.callHooks(Pointcut.OP_PRESTORAGE_RESOURCE_CREATED, hookParams);
// Perform actual DB update
ResourceTable updatedEntity = updateEntity(theRequest, theResource, entity, null, thePerformIndexing, thePerformIndexing, theUpdateTime, false, thePerformIndexing);
@ -465,6 +489,14 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
}
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void beforeCommit(boolean readOnly) {
HookParams hookParams = new HookParams()
.add(IBaseResource.class, theResource);
myInterceptorBroadcaster.callHooks(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED, hookParams);
}
});
DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(true);
if (!thePerformIndexing) {
@ -752,7 +784,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return retVal;
}
@SuppressWarnings("JpaQlInspection")
@Override
public <MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, RequestDetails theRequestDetails) {
// Notify interceptors
@ -943,8 +974,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
if (entity == null) {
if (theId.hasVersionIdPart()) {
TypedQuery<ResourceHistoryTable> q = myEntityManager
.createQuery("SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class);
TypedQuery<ResourceHistoryTable> q = myEntityManager.createQuery("SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class);
q.setParameter("RID", pid);
q.setParameter("RTYP", myResourceName);
q.setParameter("RVER", theId.getVersionIdPartAsLong());
@ -1304,7 +1334,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
/*
* Otherwise, we're not in a transaction
*/
ResourceTable savedEntity = updateInternal(theRequestDetails, theResource, thePerformIndexing, theForceUpdateVersion, theRequestDetails, entity, resourceId, oldResource);
ResourceTable savedEntity = updateInternal(theRequestDetails, theResource, thePerformIndexing, theForceUpdateVersion, entity, resourceId, oldResource);
DaoMethodOutcome outcome = toMethodOutcome(savedEntity, theResource).setCreated(false);
if (!thePerformIndexing) {
@ -1319,13 +1349,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
@Override
public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, RequestDetails theRequestDetails) {
return update(theResource, theMatchUrl, thePerformIndexing, false, theRequestDetails);
public DaoMethodOutcome update(T theResource, String theMatchUrl, RequestDetails theRequestDetails) {
return update(theResource, theMatchUrl, true, theRequestDetails);
}
@Override
public DaoMethodOutcome update(T theResource, String theMatchUrl, RequestDetails theRequestDetails) {
return update(theResource, theMatchUrl, true, theRequestDetails);
public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, RequestDetails theRequestDetails) {
return update(theResource, theMatchUrl, thePerformIndexing, false, theRequestDetails);
}
/**

View File

@ -143,7 +143,6 @@ public class DaoConfig {
private boolean myDisableHashBasedSearches;
private boolean myEnableInMemorySubscriptionMatching = true;
private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC;
private boolean mySubscriptionMatchingEnabled = true;
/**
* Constructor
@ -530,7 +529,7 @@ public class DaoConfig {
* This may be used to optionally register server interceptors directly against the DAOs.
*/
public void setInterceptors(IServerInterceptor... theInterceptor) {
setInterceptors(new ArrayList<IServerInterceptor>());
setInterceptors(new ArrayList<>());
if (theInterceptor != null && theInterceptor.length != 0) {
getInterceptors().addAll(Arrays.asList(theInterceptor));
}
@ -1308,8 +1307,7 @@ public class DaoConfig {
public void setSearchPreFetchThresholds(List<Integer> thePreFetchThresholds) {
Validate.isTrue(thePreFetchThresholds.size() > 0, "thePreFetchThresholds must not be empty");
int last = 0;
for (Integer nextInteger : thePreFetchThresholds) {
int nextInt = nextInteger.intValue();
for (Integer nextInt : thePreFetchThresholds) {
Validate.isTrue(nextInt > 0 || nextInt == -1, nextInt + " is not a valid prefetch threshold");
Validate.isTrue(nextInt != last, "Prefetch thresholds must be sequential");
Validate.isTrue(nextInt > last || nextInt == -1, "Prefetch thresholds must be sequential");
@ -1398,7 +1396,7 @@ public class DaoConfig {
*/
public boolean isSubscriptionMatchingEnabled() {
return mySubscriptionMatchingEnabled;
return myModelConfig.isSubscriptionMatchingEnabled();
}
/**
@ -1407,9 +1405,8 @@ public class DaoConfig {
* @since 3.7.0
*/
public void setSubscriptionMatchingEnabled(boolean theSubscriptionMatchingEnabled) {
mySubscriptionMatchingEnabled = theSubscriptionMatchingEnabled;
myModelConfig.setSubscriptionMatchingEnabled(theSubscriptionMatchingEnabled);
}
public ModelConfig getModelConfig() {

View File

@ -44,12 +44,8 @@ public class DaoSearchParamProvider implements ISearchParamProvider {
}
@Override
public <SP extends IBaseResource> void refreshCache(BaseSearchParamRegistry<SP> theSearchParamRegistry, long theRefreshInterval) {
public <SP extends IBaseResource> int refreshCache(BaseSearchParamRegistry<SP> theSearchParamRegistry, long theRefreshInterval) {
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.execute(t->{
theSearchParamRegistry.doRefresh(theRefreshInterval);
return null;
});
return txTemplate.execute(t-> theSearchParamRegistry.doRefresh(theRefreshInterval));
}
}

View File

@ -233,7 +233,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
Integer originalOrder = originalRequestOrder.get(nextReqEntry);
Entry nextRespEntry = response.getEntry().get(originalOrder);
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails();
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails(theRequestDetails);
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
requestDetails.setRequestType(RequestTypeEnum.GET);
requestDetails.setServer(theRequestDetails.getServer());
@ -492,7 +492,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(nextResource);
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
if (theUpdatedEntities.contains(nextOutcome.getEntity())) {
updateInternal(theRequestDetails, nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
updateInternal(theRequestDetails, nextResource, true, false, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
} else if (!theNonUpdatedEntities.contains(nextOutcome.getEntity())) {
updateEntity(theRequestDetails, nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theUpdateTime, false, true);
}

View File

@ -80,7 +80,6 @@ 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 org.thymeleaf.util.ListUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -100,7 +99,6 @@ import static org.apache.commons.lang3.StringUtils.*;
* The SearchBuilder is responsible for actually forming the SQL query that handles
* searches for resources
*/
@SuppressWarnings("JpaQlInspection")
@Component
@Scope("prototype")
public class SearchBuilder implements ISearchBuilder {
@ -386,7 +384,8 @@ public class SearchBuilder implements ISearchBuilder {
List<Predicate> codePredicates = new ArrayList<>();
for (IQueryParameterType nextOr : theList) {
for (int orIdx = 0; orIdx < theList.size(); orIdx++) {
IQueryParameterType nextOr = theList.get(orIdx);
if (nextOr instanceof ReferenceParam) {
ReferenceParam ref = (ReferenceParam) nextOr;
@ -497,15 +496,16 @@ public class SearchBuilder implements ISearchBuilder {
boolean foundChainMatch = false;
String chain = ref.getChain();
String remainingChain = null;
int chainDotIndex = chain.indexOf('.');
if (chainDotIndex != -1) {
remainingChain = chain.substring(chainDotIndex + 1);
chain = chain.substring(0, chainDotIndex);
}
for (Class<? extends IBaseResource> nextType : resourceTypes) {
String chain = ref.getChain();
String remainingChain = null;
int chainDotIndex = chain.indexOf('.');
if (chainDotIndex != -1) {
remainingChain = chain.substring(chainDotIndex + 1);
chain = chain.substring(0, chainDotIndex);
}
RuntimeResourceDefinition typeDef = myContext.getResourceDefinition(nextType);
String subResourceName = typeDef.getName();
@ -532,37 +532,29 @@ public class SearchBuilder implements ISearchBuilder {
}
}
IQueryParameterType chainValue;
if (remainingChain != null) {
if (param == null || param.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
ourLog.debug("Type {} parameter {} is not a reference, can not chain {}", nextType.getSimpleName(), chain, remainingChain);
ArrayList<IQueryParameterType> orValues = Lists.newArrayList();
for (IQueryParameterType next : theList) {
String nextValue = next.getValueAsQueryToken(myContext);
IQueryParameterType chainValue = mapReferenceChainToRawParamType(remainingChain, param, theParamName, qualifier, nextType, chain, isMeta, nextValue);
if (chainValue == null) {
continue;
}
chainValue = new ReferenceParam();
chainValue.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
((ReferenceParam) chainValue).setChain(remainingChain);
} else if (isMeta) {
IQueryParameterType type = myMatchUrlService.newInstanceType(chain);
type.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
chainValue = type;
} else {
chainValue = toParameterType(param, qualifier, resourceId);
foundChainMatch = true;
orValues.add(chainValue);
}
foundChainMatch = true;
Subquery<Long> subQ = myResourceTableQuery.subquery(Long.class);
Root<ResourceTable> subQfrom = subQ.from(ResourceTable.class);
subQ.select(subQfrom.get("myId").as(Long.class));
List<List<? extends IQueryParameterType>> andOrParams = new ArrayList<>();
andOrParams.add(Collections.singletonList(chainValue));
andOrParams.add(orValues);
/*
* We're doing a chain call, so push the current query root
* and predicate list down and put new ones at the top of the
* stack and run a subuery
* stack and run a subquery
*/
Root<ResourceTable> stackRoot = myResourceTableRoot;
ArrayList<Predicate> stackPredicates = myPredicates;
@ -574,9 +566,11 @@ public class SearchBuilder implements ISearchBuilder {
// Create the subquery predicates
myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), subResourceName));
myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted")));
searchForIdsWithAndOr(subResourceName, chain, andOrParams);
subQ.where(toArray(myPredicates));
if (foundChainMatch) {
searchForIdsWithAndOr(subResourceName, chain, andOrParams);
subQ.where(toArray(myPredicates));
}
/*
* Pop the old query root and predicate list back
@ -594,6 +588,10 @@ public class SearchBuilder implements ISearchBuilder {
if (!foundChainMatch) {
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidParameterChain", theParamName + '.' + ref.getChain()));
}
myPredicates.add(myBuilder.or(toArray(codePredicates)));
return;
}
} else {
@ -605,6 +603,28 @@ public class SearchBuilder implements ISearchBuilder {
myPredicates.add(myBuilder.or(toArray(codePredicates)));
}
private IQueryParameterType mapReferenceChainToRawParamType(String remainingChain, RuntimeSearchParam param, String theParamName, String qualifier, Class<? extends IBaseResource> nextType, String chain, boolean isMeta, String resourceId) {
IQueryParameterType chainValue;
if (remainingChain != null) {
if (param == null || param.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
ourLog.debug("Type {} parameter {} is not a reference, can not chain {}", nextType.getSimpleName(), chain, remainingChain);
return null;
}
chainValue = new ReferenceParam();
chainValue.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
((ReferenceParam) chainValue).setChain(remainingChain);
} else if (isMeta) {
IQueryParameterType type = myMatchUrlService.newInstanceType(chain);
type.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
chainValue = type;
} else {
chainValue = toParameterType(param, qualifier, resourceId);
}
return chainValue;
}
private void addPredicateResourceId(List<List<? extends IQueryParameterType>> theValues) {
for (List<? extends IQueryParameterType> nextValue : theValues) {
Set<Long> orPids = new HashSet<>();
@ -795,24 +815,27 @@ public class SearchBuilder implements ISearchBuilder {
private void addPredicateToken(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
Join<ResourceTable, ResourceIndexedSearchParamToken> join = createOrReuseJoin(JoinEnum.TOKEN, theParamName);
if (theList.get(0).getMissing() != null) {
Join<ResourceTable, ResourceIndexedSearchParamToken> join = createOrReuseJoin(JoinEnum.TOKEN, theParamName);
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
return;
}
List<Predicate> codePredicates = new ArrayList<>();
Join<ResourceTable, ResourceIndexedSearchParamToken> join = null;
for (IQueryParameterType nextOr : theList) {
if (nextOr instanceof TokenParam) {
TokenParam id = (TokenParam) nextOr;
if (id.isText()) {
addPredicateString(theResourceName, theParamName, theList);
continue;
break;
}
}
if (join == null) {
join = createOrReuseJoin(JoinEnum.TOKEN, theParamName);
}
Predicate singleCode = createPredicateToken(nextOr, theResourceName, theParamName, myBuilder, join);
codePredicates.add(singleCode);
}
@ -973,38 +996,34 @@ public class SearchBuilder implements ISearchBuilder {
@SuppressWarnings("unchecked")
private <T> Join<ResourceTable, T> createOrReuseJoin(JoinEnum theType, String theSearchParameterName) {
Join<ResourceTable, ResourceIndexedSearchParamDate> join = null;
switch (theType) {
case DATE:
join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT);
break;
case NUMBER:
join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT);
break;
case QUANTITY:
join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT);
break;
case REFERENCE:
join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
break;
case STRING:
join = myResourceTableRoot.join("myParamsString", JoinType.LEFT);
break;
case URI:
join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
break;
case TOKEN:
join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
break;
}
JoinKey key = new JoinKey(theSearchParameterName, theType);
if (!myIndexJoins.containsKey(key)) {
myIndexJoins.put(key, join);
}
return (Join<ResourceTable, T>) join;
return (Join<ResourceTable, T>) myIndexJoins.computeIfAbsent(key, k -> {
Join<ResourceTable, ResourceIndexedSearchParamDate> join = null;
switch (theType) {
case DATE:
join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT);
break;
case NUMBER:
join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT);
break;
case QUANTITY:
join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT);
break;
case REFERENCE:
join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
break;
case STRING:
join = myResourceTableRoot.join("myParamsString", JoinType.LEFT);
break;
case URI:
join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
break;
case TOKEN:
join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
break;
}
return join;
});
}
private Predicate createPredicateDate(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamDate> theFrom) {

View File

@ -89,6 +89,52 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
@Autowired
private DaoRegistry myDaoRegistry;
public BUNDLE transaction(RequestDetails theRequestDetails, BUNDLE theRequest) {
if (theRequestDetails != null) {
IServerInterceptor.ActionRequestDetails requestDetails = new IServerInterceptor.ActionRequestDetails(theRequestDetails, theRequest, "Bundle", null);
myDao.notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails);
}
String actionName = "Transaction";
BUNDLE response = processTransactionAsSubRequest((ServletRequestDetails) theRequestDetails, theRequest, actionName);
return response;
}
public BUNDLE collection(final RequestDetails theRequestDetails, BUNDLE theRequest) {
String transactionType = myVersionAdapter.getBundleType(theRequest);
if (!org.hl7.fhir.r4.model.Bundle.BundleType.COLLECTION.toCode().equals(transactionType)) {
throw new InvalidRequestException("Can not process collection Bundle of type: " + transactionType);
}
ourLog.info("Beginning storing collection with {} resources", myVersionAdapter.getEntries(theRequest).size());
long start = System.currentTimeMillis();
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
BUNDLE resp = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.BATCHRESPONSE.toCode());
List<IBaseResource> resources = new ArrayList<>();
for (final BUNDLEENTRY nextRequestEntry : myVersionAdapter.getEntries(theRequest)) {
IBaseResource resource = myVersionAdapter.getResource(nextRequestEntry);
resources.add(resource);
}
BUNDLE transactionBundle = myVersionAdapter.createBundle("transaction");
for (IBaseResource next : resources) {
BUNDLEENTRY entry = myVersionAdapter.addEntry(transactionBundle);
myVersionAdapter.setResource(entry, next);
myVersionAdapter.setRequestVerb(entry, "PUT");
myVersionAdapter.setRequestUrl(entry, next.getIdElement().toUnqualifiedVersionless().getValue());
}
transaction(theRequestDetails, transactionBundle);
return resp;
}
private void populateEntryWithOperationOutcome(BaseServerResponseException caughtEx, BUNDLEENTRY nextEntry) {
myVersionAdapter.populateEntryWithOperationOutcome(caughtEx, nextEntry);
}
@ -160,16 +206,6 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
myDao = theDao;
}
public BUNDLE transaction(RequestDetails theRequestDetails, BUNDLE theRequest) {
if (theRequestDetails != null) {
IServerInterceptor.ActionRequestDetails requestDetails = new IServerInterceptor.ActionRequestDetails(theRequestDetails, theRequest, "Bundle", null);
myDao.notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails);
}
String actionName = "Transaction";
return processTransactionAsSubRequest((ServletRequestDetails) theRequestDetails, theRequest, actionName);
}
private BUNDLE processTransactionAsSubRequest(ServletRequestDetails theRequestDetails, BUNDLE theRequest, String theActionName) {
BaseHapiFhirDao.markRequestAsProcessingSubRequest(theRequestDetails);
try {
@ -179,40 +215,6 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
}
}
public BUNDLE collection(final RequestDetails theRequestDetails, BUNDLE theRequest) {
String transactionType = myVersionAdapter.getBundleType(theRequest);
if (!org.hl7.fhir.r4.model.Bundle.BundleType.COLLECTION.toCode().equals(transactionType)) {
throw new InvalidRequestException("Can not process collection Bundle of type: " + transactionType);
}
ourLog.info("Beginning storing collection with {} resources", myVersionAdapter.getEntries(theRequest).size());
long start = System.currentTimeMillis();
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
BUNDLE resp = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.BATCHRESPONSE.toCode());
List<IBaseResource> resources = new ArrayList<>();
for (final BUNDLEENTRY nextRequestEntry : myVersionAdapter.getEntries(theRequest)) {
IBaseResource resource = myVersionAdapter.getResource(nextRequestEntry);
resources.add(resource);
}
BUNDLE transactionBundle = myVersionAdapter.createBundle("transaction");
for (IBaseResource next : resources) {
BUNDLEENTRY entry = myVersionAdapter.addEntry(transactionBundle);
myVersionAdapter.setResource(entry, next);
myVersionAdapter.setRequestVerb(entry, "PUT");
myVersionAdapter.setRequestUrl(entry, next.getIdElement().toUnqualifiedVersionless().getValue());
}
transaction(theRequestDetails, transactionBundle);
return resp;
}
private BUNDLE batch(final RequestDetails theRequestDetails, BUNDLE theRequest) {
ourLog.info("Beginning batch with {} resources", myVersionAdapter.getEntries(theRequest).size());
long start = System.currentTimeMillis();
@ -234,8 +236,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
BUNDLE subRequestBundle = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION.toCode());
myVersionAdapter.addEntry(subRequestBundle, nextRequestEntry);
BUNDLE subResponseBundle = processTransactionAsSubRequest((ServletRequestDetails) theRequestDetails, subRequestBundle, "Batch sub-request");
return subResponseBundle;
return processTransactionAsSubRequest((ServletRequestDetails) theRequestDetails, subRequestBundle, "Batch sub-request");
};
try {
@ -384,7 +385,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
Integer originalOrder = originalRequestOrder.get(nextReqEntry);
BUNDLEENTRY nextRespEntry = myVersionAdapter.getEntries(response).get(originalOrder);
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails();
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails(theRequestDetails);
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
requestDetails.setRequestType(RequestTypeEnum.GET);
requestDetails.setServer(theRequestDetails.getServer());
@ -472,10 +473,6 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
return p.parseResource(theResource.getClass(), p.encodeResourceToString(theResource));
}
public void setEntityManager(EntityManager theEntityManager) {
myEntityManager = theEntityManager;
}
private void validateDependencies() {
Validate.notNull(myEntityManager);
Validate.notNull(myContext);
@ -526,7 +523,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
}
}
if (nextResourceId.hasIdPart() && nextResourceId.getIdPart().matches("[a-zA-Z]+\\:.*") && !isPlaceholder(nextResourceId)) {
if (nextResourceId.hasIdPart() && nextResourceId.getIdPart().matches("[a-zA-Z]+:.*") && !isPlaceholder(nextResourceId)) {
throw new InvalidRequestException("Invalid placeholder ID found: " + nextResourceId.getIdPart() + " - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'");
}
@ -631,7 +628,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
version = ParameterUtil.parseETagValue(myVersionAdapter.getEntryRequestIfMatch(nextReqEntry));
}
res.setId(newIdType(parts.getResourceType(), parts.getResourceId(), version));
outcome = resourceDao.update(res, null, false, theRequestDetails);
outcome = resourceDao.update(res, null, false, false, theRequestDetails);
} else {
res.setId((String) null);
String matchUrl;
@ -641,7 +638,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
matchUrl = parts.getResourceType();
}
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
outcome = resourceDao.update(res, matchUrl, false, theRequestDetails);
outcome = resourceDao.update(res, matchUrl, false, false, theRequestDetails);
if (Boolean.TRUE.equals(outcome.getCreated())) {
conditionalRequestUrls.put(matchUrl, res.getClass());
}
@ -727,7 +724,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
if (updatedEntities.contains(nextOutcome.getEntity())) {
myDao.updateInternal(theRequestDetails, nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
myDao.updateInternal(theRequestDetails, nextResource, true, false, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
} else if (!nonUpdatedEntities.contains(nextOutcome.getEntity())) {
myDao.updateEntity(theRequestDetails, nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theUpdateTime, false, true);
}

View File

@ -99,7 +99,7 @@ public class SearchParamWithInlineReferencesExtractor {
extractInlineReferences(theResource);
myResourceLinkExtractor.extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, myDaoResourceLinkResolver);
myResourceLinkExtractor.extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, myDaoResourceLinkResolver, 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

View File

@ -29,11 +29,25 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
public class ServletSubRequestDetails extends ServletRequestDetails {
private Map<String, ArrayList<String>> myHeaders = new HashMap<>();
private Map<String, List<String>> myHeaders = new HashMap<>();
/**
* Constructor
*
* @param theRequestDetails The parent request details
*/
public ServletSubRequestDetails(ServletRequestDetails theRequestDetails) {
if (theRequestDetails != null) {
Map<String, List<String>> headers = theRequestDetails.getHeaders();
for (Map.Entry<String, List<String>> next : headers.entrySet()) {
myHeaders.put(next.getKey().toLowerCase(), next.getValue());
}
}
}
public void addHeader(String theName, String theValue) {
String lowerCase = theName.toLowerCase();
ArrayList<String> list = myHeaders.get(lowerCase);
List<String> list = myHeaders.get(lowerCase);
if (list == null) {
list = new ArrayList<>();
myHeaders.put(lowerCase, list);
@ -43,7 +57,7 @@ public class ServletSubRequestDetails extends ServletRequestDetails {
@Override
public String getHeader(String theName) {
ArrayList<String> list = myHeaders.get(theName.toLowerCase());
List<String> list = myHeaders.get(theName.toLowerCase());
if (list == null || list.isEmpty()) {
return null;
}
@ -52,7 +66,7 @@ public class ServletSubRequestDetails extends ServletRequestDetails {
@Override
public List<String> getHeaders(String theName) {
ArrayList<String> list = myHeaders.get(theName.toLowerCase());
List<String> list = myHeaders.get(theName.toLowerCase());
if (list == null || list.isEmpty()) {
return null;
}

View File

@ -391,7 +391,6 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
});
}
@SuppressWarnings("JpaQlInspection")
private void markResourceAsIndexingFailed(final long theId) {
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

View File

@ -26,9 +26,9 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
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.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.util.UrlUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@ -77,7 +77,7 @@ public class CacheWarmingSvcImpl implements ICacheWarmingSvc {
private void refreshNow(WarmCacheEntry theCacheEntry) {
String nextUrl = theCacheEntry.getUrl();
RuntimeResourceDefinition resourceDef = parseUrlResourceType(myCtx, nextUrl);
RuntimeResourceDefinition resourceDef = UrlUtil.parseUrlResourceType(myCtx, nextUrl);
IFhirResourceDao<?> callingDao = myDaoRegistry.getResourceDao(resourceDef.getName());
String queryPart = parseWarmUrlParamPart(nextUrl);
SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(queryPart, resourceDef);
@ -93,20 +93,6 @@ public class CacheWarmingSvcImpl implements ICacheWarmingSvc {
return theNextUrl.substring(paramIndex);
}
/**
* TODO: this method probably belongs in a utility class, not here
*
* @throws DataFormatException If the resource type is not known
*/
public static RuntimeResourceDefinition parseUrlResourceType(FhirContext theCtx, String theUrl) throws DataFormatException {
int paramIndex = theUrl.indexOf('?');
String resourceName = theUrl.substring(0, paramIndex);
if (resourceName.contains("/")) {
resourceName = resourceName.substring(resourceName.lastIndexOf('/') + 1);
}
return theCtx.getResourceDefinition(resourceName);
}
@PostConstruct
public void start() {
initCacheMap();
@ -120,7 +106,7 @@ public class CacheWarmingSvcImpl implements ICacheWarmingSvc {
// Validate
parseWarmUrlParamPart(next.getUrl());
parseUrlResourceType(myCtx, next.getUrl());
UrlUtil.parseUrlResourceType(myCtx, next.getUrl());
myCacheEntryToNextRefresh.put(next, 0L);
}

View File

@ -0,0 +1,27 @@
package ca.uhn.fhir.jpa.subscription;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
public interface IResourceModifiedConsumer {
void submitResourceModified(ResourceModifiedMessage theMsg);
}

View File

@ -26,17 +26,20 @@ import ca.uhn.fhir.jpa.config.BaseConfig;
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.search.warm.CacheWarmingSvcImpl;
import ca.uhn.fhir.jpa.model.interceptor.api.Hook;
import ca.uhn.fhir.jpa.model.interceptor.api.Interceptor;
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionCanonicalizer;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy;
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionStrategyEvaluator;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
import ca.uhn.fhir.util.SubscriptionUtil;
import com.google.common.annotations.VisibleForTesting;
import org.hl7.fhir.instance.model.Subscription;
@ -68,7 +71,8 @@ import java.util.concurrent.TimeUnit;
*/
@Service
@Lazy
public class SubscriptionActivatingInterceptor extends ServerOperationInterceptorAdapter {
@Interceptor(manualRegistration = true)
public class SubscriptionActivatingInterceptor {
private Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingInterceptor.class);
private static boolean ourWaitForSubscriptionActivationSynchronouslyForUnitTest;
@ -92,6 +96,8 @@ public class SubscriptionActivatingInterceptor extends ServerOperationIntercepto
private MatchUrlService myMatchUrlService;
@Autowired
private DaoConfig myDaoConfig;
@Autowired
private SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator;
public boolean activateOrRegisterSubscriptionIfRequired(final IBaseResource theSubscription) {
// Grab the value for "Subscription.channel.type" so we can see if this
@ -156,10 +162,10 @@ public class SubscriptionActivatingInterceptor extends ServerOperationIntercepto
}
}
private boolean activateSubscription(String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) {
IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao();
IBaseResource subscription = subscriptionDao.read(theSubscription.getIdElement());
subscription.setId(subscription.getIdElement().toVersionless());
ourLog.info("Activating subscription {} from status {} to {}", subscription.getIdElement().toUnqualified().getValue(), theRequestedStatus, theActiveStatus);
try {
@ -180,56 +186,71 @@ public class SubscriptionActivatingInterceptor extends ServerOperationIntercepto
submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
}
@Override
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE);
@Hook(Pointcut.OP_PRESTORAGE_RESOURCE_CREATED)
public void addStrategyTagCreated(IBaseResource theResource) {
if (isSubscription(theResource)) {
validateCriteriaAndAddStrategy(theResource);
}
}
@Override
public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) {
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE);
@Hook(Pointcut.OP_PRESTORAGE_RESOURCE_UPDATED)
public void addStrategyTagUpdated(IBaseResource theOldResource, IBaseResource theNewResource) {
if (isSubscription(theNewResource)) {
validateCriteriaAndAddStrategy(theNewResource);
}
}
@Override
public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
// TODO KHS add third type of strategy DISABLED if that subscription type is disabled on this server
public void validateCriteriaAndAddStrategy(final IBaseResource theResource) {
String criteria = mySubscriptionCanonicalizer.getCriteria(theResource);
try {
SubscriptionMatchingStrategy strategy = mySubscriptionStrategyEvaluator.determineStrategy(criteria);
mySubscriptionCanonicalizer.setMatchingStrategyTag(myFhirContext, theResource, strategy);
} catch (InvalidRequestException | DataFormatException e) {
throw new UnprocessableEntityException("Invalid subscription criteria submitted: " + criteria + " " + e.getMessage());
}
}
@Hook(Pointcut.OP_PRECOMMIT_RESOURCE_UPDATED)
public void resourceUpdated(IBaseResource theOldResource, IBaseResource theNewResource) {
submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
}
@Hook(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED)
public void resourceCreated(IBaseResource theResource) {
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE);
}
@Hook(Pointcut.OP_PRECOMMIT_RESOURCE_DELETED)
public void resourceDeleted(IBaseResource theResource) {
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE);
}
private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType) {
submitResourceModified(new ResourceModifiedMessage(myFhirContext, theNewResource, theOperationType));
if (isSubscription(theNewResource)) {
submitResourceModified(new ResourceModifiedMessage(myFhirContext, theNewResource, theOperationType));
}
}
private boolean isSubscription(IBaseResource theNewResource) {
RuntimeResourceDefinition resourceDefinition = myFhirContext.getResourceDefinition(theNewResource);
return ResourceTypeEnum.SUBSCRIPTION.getCode().equals(resourceDefinition.getName());
}
private void submitResourceModified(final ResourceModifiedMessage theMsg) {
IIdType id = theMsg.getId(myFhirContext);
if (!id.getResourceType().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) {
return;
}
switch (theMsg.getOperationType()) {
case DELETE:
mySubscriptionRegistry.unregisterSubscription(id);
mySubscriptionRegistry.unregisterSubscription(theMsg.getId(myFhirContext));
break;
case CREATE:
case UPDATE:
final IBaseResource subscription = theMsg.getNewPayload(myFhirContext);
validateCriteria(subscription);
activateAndRegisterSubscriptionIfRequiredInTransaction(subscription);
activateAndRegisterSubscriptionIfRequiredInTransaction(theMsg.getNewPayload(myFhirContext));
break;
default:
break;
}
}
public void validateCriteria(final IBaseResource theResource) {
CanonicalSubscription subscription = mySubscriptionCanonicalizer.canonicalize(theResource);
String criteria = subscription.getCriteriaString();
try {
RuntimeResourceDefinition resourceDef = CacheWarmingSvcImpl.parseUrlResourceType(myFhirContext, criteria);
myMatchUrlService.translateMatchUrl(criteria, resourceDef);
} catch (InvalidRequestException e) {
throw new UnprocessableEntityException("Invalid subscription criteria submitted: " + criteria + " " + e.getMessage());
}
}
private void activateAndRegisterSubscriptionIfRequiredInTransaction(IBaseResource theSubscription) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.execute(new TransactionCallbackWithoutResult() {

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.subscription;
*/
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorRegistry;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
import com.google.common.annotations.VisibleForTesting;
@ -37,52 +38,44 @@ import java.util.Set;
public class SubscriptionInterceptorLoader {
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionInterceptorLoader.class);
@Autowired
private SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
@Autowired
private SubscriptionActivatingInterceptor mySubscriptionActivatingInterceptor;
@Autowired
DaoConfig myDaoConfig;
@Autowired
private SubscriptionRegistry mySubscriptionRegistry;
@Autowired
private ApplicationContext myAppicationContext;
@Autowired
private SubscriptionRegistry mySubscriptionRegistry;
private IInterceptorRegistry myInterceptorRegistry;
public void registerInterceptors() {
Set<Subscription.SubscriptionChannelType> supportedSubscriptionTypes = myDaoConfig.getSupportedSubscriptionTypes();
if (!supportedSubscriptionTypes.isEmpty()) {
loadSubscriptions();
ourLog.info("Registering subscription activating interceptor");
myDaoConfig.registerInterceptor(mySubscriptionActivatingInterceptor);
myInterceptorRegistry.registerInterceptor(mySubscriptionActivatingInterceptor);
}
if (myDaoConfig.isSubscriptionMatchingEnabled()) {
mySubscriptionMatcherInterceptor.start();
ourLog.info("Registering subscription matcher interceptor");
if (mySubscriptionMatcherInterceptor == null) {
mySubscriptionMatcherInterceptor = myAppicationContext.getBean(SubscriptionMatcherInterceptor.class);
}
myDaoConfig.registerInterceptor(mySubscriptionMatcherInterceptor);
myInterceptorRegistry.registerInterceptor(mySubscriptionMatcherInterceptor);
}
}
private void loadSubscriptions() {
ourLog.info("Loading subscriptions into the SubscriptionRegistry...");
// Load subscriptions into the SubscriptionRegistry
// Activate scheduled subscription loads into the SubscriptionRegistry
myAppicationContext.getBean(SubscriptionLoader.class);
ourLog.info("...{} subscriptions loaded", mySubscriptionRegistry.size());
// Once subscriptions have been loaded, now
if (mySubscriptionActivatingInterceptor == null) {
mySubscriptionActivatingInterceptor = myAppicationContext.getBean(SubscriptionActivatingInterceptor.class);
}
}
@VisibleForTesting
public void unregisterInterceptorsForUnitTest() {
myDaoConfig.unregisterInterceptor(mySubscriptionActivatingInterceptor);
myDaoConfig.unregisterInterceptor(mySubscriptionMatcherInterceptor);
void unregisterInterceptorsForUnitTest() {
myInterceptorRegistry.unregisterInterceptor(mySubscriptionActivatingInterceptor);
myInterceptorRegistry.unregisterInterceptor(mySubscriptionMatcherInterceptor);
}
}

View File

@ -1,14 +1,16 @@
package ca.uhn.fhir.jpa.subscription;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.interceptor.api.Hook;
import ca.uhn.fhir.jpa.model.interceptor.api.Interceptor;
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.subscription.module.LinkedBlockingQueueSubscribableChannel;
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionChannelFactory;
import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage;
import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionMatchingSubscriber;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -17,7 +19,6 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/*-
@ -42,9 +43,11 @@ import javax.annotation.PreDestroy;
@Component
@Lazy
public class SubscriptionMatcherInterceptor extends ServerOperationInterceptorAdapter {
@Interceptor(manualRegistration = true)
public class SubscriptionMatcherInterceptor implements IResourceModifiedConsumer {
private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherInterceptor.class);
private static final String SUBSCRIPTION_MATCHING_CHANNEL_NAME = "subscription-matching";
static final String SUBSCRIPTION_STATUS = "Subscription.status";
static final String SUBSCRIPTION_TYPE = "Subscription.channel.type";
private SubscribableChannel myProcessingChannel;
@ -63,32 +66,36 @@ public class SubscriptionMatcherInterceptor extends ServerOperationInterceptorAd
super();
}
@PostConstruct
public void start() {
if (myProcessingChannel == null) {
myProcessingChannel = mySubscriptionChannelFactory.newMatchingChannel("subscription-matching");
myProcessingChannel = mySubscriptionChannelFactory.newMatchingChannel(SUBSCRIPTION_MATCHING_CHANNEL_NAME);
}
myProcessingChannel.subscribe(mySubscriptionMatchingSubscriber);
ourLog.info("Subscription Matching Subscriber subscribed to Matching Channel {} with name {}", myProcessingChannel.getClass().getName(), SUBSCRIPTION_MATCHING_CHANNEL_NAME);
}
@SuppressWarnings("unused")
@PreDestroy
public void preDestroy() {
myProcessingChannel.unsubscribe(mySubscriptionMatchingSubscriber);
if (myProcessingChannel != null) {
myProcessingChannel.unsubscribe(mySubscriptionMatchingSubscriber);
}
}
@Override
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
@Hook(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED)
public void resourceCreated(IBaseResource theResource) {
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE);
}
@Override
public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) {
@Hook(Pointcut.OP_PRECOMMIT_RESOURCE_DELETED)
public void resourceDeleted(IBaseResource theResource) {
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE);
}
@Override
public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
@Hook(Pointcut.OP_PRECOMMIT_RESOURCE_UPDATED)
public void resourceUpdated(IBaseResource theOldResource, IBaseResource theNewResource) {
submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
}
@ -97,8 +104,9 @@ public class SubscriptionMatcherInterceptor extends ServerOperationInterceptorAd
submitResourceModified(msg);
}
protected void sendToProcessingChannel(final ResourceModifiedMessage theMessage) {
private void sendToProcessingChannel(final ResourceModifiedMessage theMessage) {
ourLog.trace("Sending resource modified message to processing channel");
Validate.notNull(myProcessingChannel, "A SubscriptionMatcherInterceptor has been registered without calling start() on it.");
myProcessingChannel.send(new ResourceModifiedJsonMessage(theMessage));
}
@ -109,12 +117,13 @@ public class SubscriptionMatcherInterceptor extends ServerOperationInterceptorAd
/**
* This is an internal API - Use with caution!
*/
@Override
public void submitResourceModified(final ResourceModifiedMessage theMsg) {
sendToProcessingChannel(theMsg);
}
@VisibleForTesting
public LinkedBlockingQueueSubscribableChannel getProcessingChannelForUnitTest() {
LinkedBlockingQueueSubscribableChannel getProcessingChannelForUnitTest() {
return (LinkedBlockingQueueSubscribableChannel) myProcessingChannel;
}
}

View File

@ -27,7 +27,6 @@ import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
@ -42,6 +41,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.util.ParametersUtil;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.ValidateUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate;
@ -55,15 +55,15 @@ import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.stream.Collectors;
@ -72,10 +72,10 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Service
public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc, ApplicationContextAware {
public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc {
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTriggeringProvider.class);
public static final int DEFAULT_MAX_SUBMIT = 10000;
private static final int DEFAULT_MAX_SUBMIT = 10000;
@Autowired
private FhirContext myFhirContext;
@ -88,11 +88,10 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
@Autowired
private MatchUrlService myMatchUrlService;
@Autowired
private SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
private IResourceModifiedConsumer myResourceModifiedConsumer;
private final List<SubscriptionTriggeringJobDetails> myActiveJobs = new ArrayList<>();
private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT;
private ApplicationContext myAppCtx;
private ExecutorService myExecutorService;
@Override
@ -105,7 +104,7 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
if (theSubscriptionId != null) {
IFhirResourceDao<?> subscriptionDao = myDaoRegistry.getSubscriptionDao();
IIdType subscriptionId = theSubscriptionId;
if (subscriptionId.hasResourceType() == false) {
if (!subscriptionId.hasResourceType()) {
subscriptionId = subscriptionId.withResourceType(ResourceTypeEnum.SUBSCRIPTION.getCode());
}
subscriptionDao.read(subscriptionId);
@ -128,7 +127,7 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
// Search URLs must be valid
for (StringParam next : searchUrls) {
if (next.getValue().contains("?") == false) {
if (!next.getValue().contains("?")) {
throw new InvalidRequestException("Search URL is not valid (must be in the form \"[resource type]?[optional params]\")");
}
}
@ -163,7 +162,7 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
return;
}
String activeJobIds = myActiveJobs.stream().map(t -> t.getJobId()).collect(Collectors.joining(", "));
String activeJobIds = myActiveJobs.stream().map(SubscriptionTriggeringJobDetails::getJobId).collect(Collectors.joining(", "));
ourLog.info("Starting pass: currently have {} active job IDs: {}", myActiveJobs.size(), activeJobIds);
SubscriptionTriggeringJobDetails activeJob = myActiveJobs.get(0);
@ -210,7 +209,7 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
// If we don't have an active search started, and one needs to be.. start it
if (isBlank(theJobDetails.getCurrentSearchUuid()) && theJobDetails.getRemainingSearchUrls().size() > 0 && totalSubmitted < myMaxSubmitPerPass) {
String nextSearchUrl = theJobDetails.getRemainingSearchUrls().remove(0);
RuntimeResourceDefinition resourceDef = CacheWarmingSvcImpl.parseUrlResourceType(myFhirContext, nextSearchUrl);
RuntimeResourceDefinition resourceDef = UrlUtil.parseUrlResourceType(myFhirContext, nextSearchUrl);
String queryPart = nextSearchUrl.substring(nextSearchUrl.indexOf('?'));
String resourceType = resourceDef.getName();
@ -290,7 +289,7 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
private Future<Void> submitResource(String theSubscriptionId, String theResourceIdToTrigger) {
org.hl7.fhir.r4.model.IdType resourceId = new org.hl7.fhir.r4.model.IdType(theResourceIdToTrigger);
IFhirResourceDao<? extends IBaseResource> dao = myDaoRegistry.getResourceDao(resourceId.getResourceType());
IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceId.getResourceType());
IBaseResource resourceToTrigger = dao.read(resourceId);
return submitResource(theSubscriptionId, resourceToTrigger);
@ -306,7 +305,7 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
return myExecutorService.submit(() -> {
for (int i = 0; ; i++) {
try {
mySubscriptionMatcherInterceptor.submitResourceModified(msg);
myResourceModifiedConsumer.submitResourceModified(msg);
break;
} catch (Exception e) {
if (i >= 3) {
@ -329,11 +328,6 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
myAppCtx = applicationContext;
}
/**
* Sets the maximum number of resources that will be submitted in a single pass
*/
@ -346,7 +340,6 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
myMaxSubmitPerPass = maxSubmitPerPass;
}
@SuppressWarnings("unchecked")
@PostConstruct
public void start() {
LinkedBlockingQueue<Runnable> executorQueue = new LinkedBlockingQueue<>(1000);
@ -393,67 +386,67 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
private String myCurrentSearchResourceType;
private int myCurrentSearchLastUploadedIndex;
public Integer getCurrentSearchCount() {
Integer getCurrentSearchCount() {
return myCurrentSearchCount;
}
public void setCurrentSearchCount(Integer theCurrentSearchCount) {
void setCurrentSearchCount(Integer theCurrentSearchCount) {
myCurrentSearchCount = theCurrentSearchCount;
}
public String getCurrentSearchResourceType() {
String getCurrentSearchResourceType() {
return myCurrentSearchResourceType;
}
public void setCurrentSearchResourceType(String theCurrentSearchResourceType) {
void setCurrentSearchResourceType(String theCurrentSearchResourceType) {
myCurrentSearchResourceType = theCurrentSearchResourceType;
}
public String getJobId() {
String getJobId() {
return myJobId;
}
public void setJobId(String theJobId) {
void setJobId(String theJobId) {
myJobId = theJobId;
}
public String getSubscriptionId() {
String getSubscriptionId() {
return mySubscriptionId;
}
public void setSubscriptionId(String theSubscriptionId) {
void setSubscriptionId(String theSubscriptionId) {
mySubscriptionId = theSubscriptionId;
}
public List<String> getRemainingResourceIds() {
List<String> getRemainingResourceIds() {
return myRemainingResourceIds;
}
public void setRemainingResourceIds(List<String> theRemainingResourceIds) {
void setRemainingResourceIds(List<String> theRemainingResourceIds) {
myRemainingResourceIds = theRemainingResourceIds;
}
public List<String> getRemainingSearchUrls() {
List<String> getRemainingSearchUrls() {
return myRemainingSearchUrls;
}
public void setRemainingSearchUrls(List<String> theRemainingSearchUrls) {
void setRemainingSearchUrls(List<String> theRemainingSearchUrls) {
myRemainingSearchUrls = theRemainingSearchUrls;
}
public String getCurrentSearchUuid() {
String getCurrentSearchUuid() {
return myCurrentSearchUuid;
}
public void setCurrentSearchUuid(String theCurrentSearchUuid) {
void setCurrentSearchUuid(String theCurrentSearchUuid) {
myCurrentSearchUuid = theCurrentSearchUuid;
}
public int getCurrentSearchLastUploadedIndex() {
int getCurrentSearchLastUploadedIndex() {
return myCurrentSearchLastUploadedIndex;
}
public void setCurrentSearchLastUploadedIndex(int theCurrentSearchLastUploadedIndex) {
void setCurrentSearchLastUploadedIndex(int theCurrentSearchLastUploadedIndex) {
myCurrentSearchLastUploadedIndex = theCurrentSearchLastUploadedIndex;
}
}

View File

@ -42,10 +42,7 @@ public class DaoSubscriptionProvider implements ISubscriptionProvider {
@Override
public IBundleProvider search(SearchParameterMap theMap) {
IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao();
RequestDetails req = new ServletSubRequestDetails();
req.setSubRequest(true);
return subscriptionDao.search(theMap, req);
return subscriptionDao.search(theMap);
}
@Override

View File

@ -21,10 +21,11 @@ package ca.uhn.fhir.jpa.subscription.dbmatcher;
*/
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult;
import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher;
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -43,16 +44,19 @@ public class CompositeInMemoryDaoSubscriptionMatcher implements ISubscriptionMat
}
@Override
public SubscriptionMatchResult match(String criteria, ResourceModifiedMessage msg) {
public SubscriptionMatchResult match(CanonicalSubscription theSubscription, ResourceModifiedMessage theMsg) {
SubscriptionMatchResult result;
if (myDaoConfig.isEnableInMemorySubscriptionMatching()) {
result = myInMemorySubscriptionMatcher.match(criteria, msg);
if (!result.supported()) {
ourLog.info("Criteria {} not supported by InMemoryMatcher: {}. Reverting to DatabaseMatcher", criteria, result.getUnsupportedReason());
result = myDaoSubscriptionMatcher.match(criteria, msg);
result = myInMemorySubscriptionMatcher.match(theSubscription, theMsg);
if (result.supported()) {
// TODO KHS test
result.setInMemory(true);
} else {
ourLog.info("Criteria {} for Subscription {} not supported by InMemoryMatcher: {}. Reverting to DatabaseMatcher", theSubscription.getCriteriaString(), theSubscription.getIdElementString(), result.getUnsupportedReason());
result = myDaoSubscriptionMatcher.match(theSubscription, theMsg);
}
} else {
result = myDaoSubscriptionMatcher.match(criteria, msg);
result = myDaoSubscriptionMatcher.match(theSubscription, theMsg);
}
return result;
}

View File

@ -24,14 +24,13 @@ 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.provider.ServletSubRequestDetails;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult;
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.IIdType;
import org.slf4j.Logger;
@ -49,10 +48,11 @@ public class DaoSubscriptionMatcher implements ISubscriptionMatcher {
MatchUrlService myMatchUrlService;
@Override
public SubscriptionMatchResult match(String criteria, ResourceModifiedMessage msg) {
IIdType id = msg.getId(myCtx);
public SubscriptionMatchResult match(CanonicalSubscription theSubscription, ResourceModifiedMessage theMsg) {
IIdType id = theMsg.getId(myCtx);
String resourceType = id.getResourceType();
String resourceId = id.getIdPart();
String criteria = theSubscription.getCriteriaString();
// run the subscriptions query and look for matches, add the id as part of the criteria to avoid getting matches of previous resources rather than the recent resource
criteria += "&_id=" + resourceType + "/" + resourceId;
@ -61,23 +61,20 @@ public class DaoSubscriptionMatcher implements ISubscriptionMatcher {
ourLog.debug("Subscription check found {} results for query: {}", results.size(), criteria);
return new SubscriptionMatchResult(results.size() > 0, "DATABASE");
return SubscriptionMatchResult.fromBoolean(results.size() > 0);
}
/**
* Search based on a query criteria
*/
protected IBundleProvider performSearch(String theCriteria) {
private IBundleProvider performSearch(String theCriteria) {
IFhirResourceDao<?> subscriptionDao = myDaoRegistry.getSubscriptionDao();
RuntimeResourceDefinition responseResourceDef = subscriptionDao.validateCriteriaAndReturnResourceDefinition(theCriteria);
SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(theCriteria, responseResourceDef);
RequestDetails req = new ServletSubRequestDetails();
req.setSubRequest(true);
IFhirResourceDao<? extends IBaseResource> responseDao = myDaoRegistry.getResourceDao(responseResourceDef.getImplementingClass());
responseCriteriaUrl.setLoadSynchronousUpTo(1);
return responseDao.search(responseCriteriaUrl, req);
return responseDao.search(responseCriteriaUrl);
}
}

View File

@ -25,9 +25,9 @@ import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.data.*;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.ScrollableResultsIterator;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -137,6 +137,8 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
private int myFetchSize = DEFAULT_FETCH_SIZE;
private ApplicationContext myApplicationContext;
private TransactionTemplate myTxTemplate;
@Autowired
private PlatformTransactionManager myTransactionManager;
/**
* @param theAdd If true, add the code. If false, remove the code.
@ -368,9 +370,6 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
}
}
@Autowired
private PlatformTransactionManager myTransactionManager;
@Override
@Transactional
public void deleteConceptMapAndChildren(ResourceTable theResourceTable) {
@ -391,7 +390,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
txTemplate.execute(t->{
txTemplate.execute(t -> {
theDao.deleteInBatch(link);
return null;
});
@ -1190,6 +1189,22 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
termConceptMap.setResource(theResourceTable);
termConceptMap.setUrl(theConceptMap.getUrl());
String source = theConceptMap.hasSourceUriType() ? theConceptMap.getSourceUriType().getValueAsString() : null;
String target = theConceptMap.hasTargetUriType() ? theConceptMap.getTargetUriType().getValueAsString() : null;
/*
* If this is a mapping between "resources" instead of purely between
* "concepts" (this is a weird concept that is technically possible, at least as of
* FHIR R4), don't try to store the mappings.
*
* See here for a description of what that is:
* http://hl7.org/fhir/conceptmap.html#bnr
*/
if ("StructureDefinition".equals(new IdType(source).getResourceType()) ||
"StructureDefinition".equals(new IdType(target).getResourceType())) {
return;
}
/*
* For now we always delete old versions. At some point, it would be nice to allow configuration to keep old versions.
*/
@ -1202,11 +1217,9 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
Optional<TermConceptMap> optionalExistingTermConceptMapByUrl = myConceptMapDao.findTermConceptMapByUrl(conceptMapUrl);
if (!optionalExistingTermConceptMapByUrl.isPresent()) {
try {
String source = theConceptMap.hasSourceUriType() ? theConceptMap.getSourceUriType().getValueAsString() : null;
if (isNotBlank(source)) {
termConceptMap.setSource(source);
}
String target = theConceptMap.hasTargetUriType() ? theConceptMap.getTargetUriType().getValueAsString() : null;
if (isNotBlank(target)) {
termConceptMap.setTarget(target);
}
@ -1244,12 +1257,12 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
if (element.hasTarget()) {
TermConceptMapGroupElementTarget termConceptMapGroupElementTarget;
for (ConceptMap.TargetElementComponent target : element.getTarget()) {
for (ConceptMap.TargetElementComponent elementTarget : element.getTarget()) {
termConceptMapGroupElementTarget = new TermConceptMapGroupElementTarget();
termConceptMapGroupElementTarget.setConceptMapGroupElement(termConceptMapGroupElement);
termConceptMapGroupElementTarget.setCode(target.getCode());
termConceptMapGroupElementTarget.setDisplay(target.getDisplay());
termConceptMapGroupElementTarget.setEquivalence(target.getEquivalence());
termConceptMapGroupElementTarget.setCode(elementTarget.getCode());
termConceptMapGroupElementTarget.setDisplay(elementTarget.getDisplay());
termConceptMapGroupElementTarget.setEquivalence(elementTarget.getEquivalence());
myConceptMapGroupElementTargetDao.save(termConceptMapGroupElementTarget);
if (codesSaved++ % 250 == 0) {

View File

@ -25,11 +25,15 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.InstantType;
import org.hl7.fhir.r4.model.Patient;
import javax.persistence.*;
import java.io.IOException;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
@ -165,10 +169,21 @@ public class TestUtil {
ourLog.info("Sleeping for {}ms", timeToSleep);
Thread.sleep(timeToSleep);
} catch (InterruptedException theE) {
theE.printStackTrace();
ourLog.error("Interrupted", theE);
}
}
}
public static void clearAllStaticFieldsForUnitTest() {
ca.uhn.fhir.util.TestUtil.clearAllStaticFieldsForUnitTest();
}
public static InstantType getTimestamp(IBaseResource resource) {
return new InstantType(new Date(resource.getMeta().getLastUpdated().getTime()));
}
public static void sleepOneClick() {
sleepAtLeast(1);
}
}

View File

@ -0,0 +1,92 @@
package ca.uhn.fhir.jpa.config;
import net.ttddyy.dsproxy.ExecutionInfo;
import net.ttddyy.dsproxy.QueryInfo;
import net.ttddyy.dsproxy.proxy.ParameterSetOperation;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
public class CaptureQueriesListener implements ProxyDataSourceBuilder.SingleQueryExecution {
private static final LinkedList<Query> LAST_N_QUERIES = new LinkedList<>();
@Override
public void execute(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) {
synchronized (LAST_N_QUERIES) {
for (QueryInfo next : queryInfoList) {
String sql = next.getQuery();
List<String> params;
if (next.getParametersList().size() > 0 && next.getParametersList().get(0).size() > 0) {
List<ParameterSetOperation> values = next
.getParametersList()
.get(0);
params = values.stream()
.map(t -> t.getArgs()[1])
.map(t -> t != null ? t.toString() : "NULL")
.collect(Collectors.toList());
} else {
params = new ArrayList<>();
}
LAST_N_QUERIES.add(0, new Query(sql, params));
}
while (LAST_N_QUERIES.size() > 100) {
LAST_N_QUERIES.removeLast();
}
}
}
public static class Query {
private final String myThreadName = Thread.currentThread().getName();
private final String mySql;
private final List<String> myParams;
Query(String theSql, List<String> theParams) {
mySql = theSql;
myParams = Collections.unmodifiableList(theParams);
}
public String getThreadName() {
return myThreadName;
}
public String getSql(boolean theInlineParams, boolean theFormat) {
String retVal = mySql;
if (theFormat) {
retVal = new BasicFormatterImpl().format(retVal);
}
if (theInlineParams) {
List<String> nextParams = new ArrayList<>(myParams);
while (retVal.contains("?") && nextParams.size() > 0) {
int idx = retVal.indexOf("?");
retVal = retVal.substring(0, idx) + nextParams.remove(0) + retVal.substring(idx + 1);
}
}
return retVal;
}
}
public static void clear() {
synchronized (LAST_N_QUERIES) {
LAST_N_QUERIES.clear();
}
}
/**
* Index 0 is newest!
*/
public static ArrayList<Query> getLastNQueries() {
synchronized (LAST_N_QUERIES) {
return new ArrayList<>(LAST_N_QUERIES);
}
}
}

View File

@ -100,6 +100,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
// .logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
// .countQuery(new ThreadQueryCountHolder())
.beforeQuery(new BlockLargeNumbersOfParamsListener())
.afterQuery(new CaptureQueriesListener())
.countQuery(singleQueryCountHolder())
.build();

View File

@ -1,7 +1,10 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.CaptureQueriesListener;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorRegistry;
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
@ -51,7 +54,9 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static ca.uhn.fhir.util.TestUtil.randomizeLocale;
@ -84,10 +89,13 @@ public abstract class BaseJpaTest {
protected IRequestOperationCallback myRequestOperationCallback = mock(IRequestOperationCallback.class);
@Autowired
protected DatabaseBackedPagingProvider myDatabaseBackedPagingProvider;
@Autowired
protected IInterceptorRegistry myInterceptorRegistry;
@After
public void afterPerformCleanup() {
BaseHapiFhirResourceDao.setDisableIncrementOnUpdateForUnitTest(false);
CaptureQueriesListener.clear();
}
@After
@ -128,6 +136,12 @@ public abstract class BaseJpaTest {
when(mySrd.getHeaders(eq(JpaConstants.HEADER_META_SNAPSHOT_MODE))).thenReturn(new ArrayList<>());
}
protected CountDownLatch registerLatchHookInterceptor(int theCount, Pointcut theLatchPointcut) {
CountDownLatch deliveryLatch = new CountDownLatch(theCount);
myInterceptorRegistry.registerAnonymousHookForUnitTest(theLatchPointcut, Integer.MAX_VALUE, t -> deliveryLatch.countDown());
return deliveryLatch;
}
protected abstract FhirContext getContext();
protected abstract PlatformTransactionManager getTxManager();
@ -431,6 +445,20 @@ public abstract class BaseJpaTest {
}
}
public static void waitForTrue(Supplier<Boolean> theList) {
StopWatch sw = new StopWatch();
while (!theList.get() && sw.getMillis() <= 16000) {
try {
Thread.sleep(50);
} catch (InterruptedException theE) {
throw new Error(theE);
}
}
if (sw.getMillis() >= 16000) {
fail("Waited " + sw.toString() + " and is still false");
}
}
public static void waitForSize(int theTarget, Callable<Number> theCallable) throws Exception {
waitForSize(theTarget, 10000, theCallable);
}

View File

@ -56,7 +56,7 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
@Qualifier("myResourceCountsCache")
protected ResourceCountCache myResourceCountsCache;
@Autowired
protected ISearchParamRegistry mySearchParamRegsitry;
protected ISearchParamRegistry mySearchParamRegistry;
@Autowired
protected ApplicationContext myAppCtx;
@Autowired
@ -181,8 +181,6 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
@Qualifier("myValueSetDaoDstu2")
protected IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt> myValueSetDao;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@Autowired
protected SubscriptionLoader mySubscriptionLoader;
@Before

View File

@ -2,8 +2,8 @@ package ca.uhn.fhir.jpa.dao.dstu2;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
@ -70,7 +70,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
threadIdSp.setXpathUsage(XPathUsageTypeEnum.NORMAL);
threadIdSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
mySearchParameterDao.create(threadIdSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Communication com = new Communication();
com.setStatus(CommunicationStatusEnum.IN_PROGRESS);
@ -163,7 +163,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
sp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Practitioner pract = new Practitioner();
pract.setId("A");
@ -194,7 +194,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
eyeColourSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
mySearchParameterDao.create(eyeColourSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.setActive(true);
@ -215,7 +215,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
attendingSp.addTarget(ResourceTypeEnum.PRACTITIONER);
IIdType spId = mySearchParameterDao.create(attendingSp, mySrd).getId().toUnqualifiedVersionless();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Practitioner p1 = new Practitioner();
p1.getName().addFamily("P1");
@ -265,7 +265,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
identifierSp.setStatus(ConformanceResourceStatusEnum.RETIRED);
mySearchParameterDao.create(identifierSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p = new Patient();
p.addName().addGiven("G");
@ -307,7 +307,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
identifierSp.setStatus(ConformanceResourceStatusEnum.RETIRED);
mySearchParameterDao.create(identifierSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p = new Patient();
p.addName().addGiven("G");
@ -339,7 +339,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.addTarget(ResourceTypeEnum.ORGANIZATION);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.addName().addFamily("P1");
@ -382,7 +382,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.addTarget(ResourceTypeEnum.PATIENT);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.addName().addFamily("P1");
@ -435,7 +435,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.addName().addFamily("P1");
@ -489,7 +489,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
eyeColourSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
mySearchParameterDao.create(eyeColourSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.setActive(true);
@ -522,7 +522,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.addTarget(ResourceTypeEnum.ORGANIZATION);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().addFamily("P2");
@ -558,7 +558,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.addTarget(ResourceTypeEnum.ORGANIZATION);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().addFamily("P2");
@ -594,7 +594,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Appointment apt = new Appointment();
apt.setStatus(AppointmentStatusEnum.ARRIVED);
@ -635,7 +635,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().addFamily("P2");
@ -671,7 +671,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().addFamily("P2");
@ -708,7 +708,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.addTarget(ResourceTypeEnum.APPOINTMENT);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Appointment apt = new Appointment();
apt.setStatus(AppointmentStatusEnum.ARRIVED);
@ -747,7 +747,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Appointment apt = new Appointment();
apt.setStatus(AppointmentStatusEnum.ARRIVED);
@ -789,7 +789,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.addTarget(ResourceTypeEnum.OBSERVATION);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Appointment apt = new Appointment();
apt.setStatus(AppointmentStatusEnum.ARRIVED);
@ -830,7 +830,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().addFamily("P2");
@ -867,7 +867,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
fooSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
IIdType spId = mySearchParameterDao.create(fooSp, mySrd).getId().toUnqualifiedVersionless();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.addIdentifier().setSystem("FOO123").setValue("BAR678");
@ -912,7 +912,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
IIdType spId = mySearchParameterDao.create(fooSp, mySrd).getId().toUnqualifiedVersionless();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.addIdentifier().setSystem("http://AAA").setValue("BAR678");
@ -957,7 +957,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
fooSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
IIdType spId = mySearchParameterDao.create(fooSp, mySrd).getId().toUnqualifiedVersionless();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.setGender(AdministrativeGenderEnum.MALE);
@ -988,7 +988,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
// Delete the param
mySearchParameterDao.delete(spId, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
myResourceReindexingSvc.markAllResourcesForReindexing();
myResourceReindexingSvc.forceReindexingPass();
myResourceReindexingSvc.forceReindexingPass();
@ -1016,7 +1016,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
fooSp.setStatus(ConformanceResourceStatusEnum.DRAFT);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.setGender(AdministrativeGenderEnum.MALE);

View File

@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.dstu2.composite.*;
@ -19,7 +20,6 @@ import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -374,6 +374,9 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
patient.addIdentifier().setSystem("urn:system").setValue("001");
id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
long betweenTime = System.currentTimeMillis();
IIdType id2;
{
@ -820,8 +823,6 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
@Test
public void testSearchLastUpdatedParamWithComparator() throws InterruptedException {
String methodName = "testSearchLastUpdatedParamWithComparator";
IIdType id0;
{
Patient patient = new Patient();
@ -829,18 +830,16 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
id0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
int sleep = 100;
long start = System.currentTimeMillis();
Thread.sleep(sleep);
TestUtil.sleepOneClick();
DateTimeDt beforeAny = new DateTimeDt(new Date(), TemporalPrecisionEnum.MILLI);
IIdType id1a;
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("001");
id1a = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
IIdType id1b;
{
Patient patient = new Patient();
@ -853,11 +852,12 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
InstantDt id1bpublished = ResourceMetadataKeyEnum.PUBLISHED.get(myPatientDao.read(id1b, mySrd));
ourLog.info("Res 3: {}", id1bpublished.getValueAsString());
Thread.sleep(sleep);
TestUtil.sleepOneClick();
long end = System.currentTimeMillis();
SearchParameterMap params;
Date startDate = new Date(start);
TestUtil.sleepOneClick();
Date endDate = new Date(end);
DateTimeDt startDateTime = new DateTimeDt(startDate, TemporalPrecisionEnum.MILLI);
DateTimeDt endDateTime = new DateTimeDt(endDate, TemporalPrecisionEnum.MILLI);

View File

@ -56,7 +56,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import static org.junit.Assert.*;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
@RunWith(SpringJUnit4ClassRunner.class)
@ -215,7 +215,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@Autowired
protected ISearchParamPresenceSvc mySearchParamPresenceSvc;
@Autowired
protected ISearchParamRegistry mySearchParamRegsitry;
protected ISearchParamRegistry mySearchParamRegistry;
@Autowired
protected IStaleSearchDeletingSvc myStaleSearchDeletingSvc;
@Autowired
@ -256,8 +256,6 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
protected ITermConceptMapGroupElementTargetDao myTermConceptMapGroupElementTargetDao;
@Autowired
private JpaValidationSupportChainDstu3 myJpaValidationSupportChainDstu3;
@Autowired
protected ISearchParamRegistry mySearchParamRegistry;
@After()
public void afterCleanupDao() {
@ -306,7 +304,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@Before
@Transactional()
public void beforePurgeDatabase() {
purgeDatabase(myDaoConfig, mySystemDao, myResourceReindexingSvc, mySearchCoordinatorSvc, mySearchParamRegsitry);
purgeDatabase(myDaoConfig, mySystemDao, myResourceReindexingSvc, mySearchCoordinatorSvc, mySearchParamRegistry);
}
@Before

View File

@ -139,7 +139,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
sp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
org.hl7.fhir.dstu3.model.Practitioner pract = new org.hl7.fhir.dstu3.model.Practitioner();
pract.setId("A");
@ -169,7 +169,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
eyeColourSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(eyeColourSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.setActive(true);
@ -191,7 +191,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
attendingSp.getTarget().add(new CodeType("Practitioner"));
IIdType spId = mySearchParameterDao.create(attendingSp, mySrd).getId().toUnqualifiedVersionless();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Practitioner p1 = new Practitioner();
p1.addName().setFamily("P1");
@ -231,7 +231,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
threadIdSp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
threadIdSp.setStatus(Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(threadIdSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Communication com = new Communication();
com.setStatus(Communication.CommunicationStatus.INPROGRESS);
@ -277,7 +277,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
sp.addTarget("Condition");
sp.addTarget("Observation");
mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Condition condition = new Condition();
condition.getCode().setText("A condition");
@ -313,7 +313,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
sp.setType(Enumerations.SearchParamType.TOKEN);
sp.setExpression("MedicationRequest.reasonCode | ProcedureRequest.reasonCode");
mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
MedicationRequest mr = new MedicationRequest();
mr.addReasonCode().addCoding().setSystem("foo").setCode("bar");
@ -343,7 +343,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.getTarget().add(new CodeType("Organization"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.addName().setFamily("P1");
@ -387,7 +387,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.getTarget().add(new CodeType("Patient"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.addName().setFamily("P1");
@ -441,7 +441,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.addName().setFamily("P1");
@ -496,7 +496,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
eyeColourSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(eyeColourSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.setActive(true);
@ -530,7 +530,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.getTarget().add(new CodeType("Organization"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().setFamily("P2");
@ -568,7 +568,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.getTarget().add(new CodeType("Organization"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().setFamily("P2");
@ -605,7 +605,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Appointment apt = new Appointment();
apt.setStatus(AppointmentStatus.ARRIVED);
@ -647,7 +647,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().setFamily("P2");
@ -684,7 +684,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().setFamily("P2");
@ -722,7 +722,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.getTarget().add(new CodeType("Appointment"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Appointment apt = new Appointment();
apt.setStatus(AppointmentStatus.ARRIVED);
@ -764,7 +764,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Appointment apt = new Appointment();
apt.setStatus(AppointmentStatus.ARRIVED);
@ -807,7 +807,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.getTarget().add(new CodeType("Observation"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Appointment apt = new Appointment();
apt.setStatus(AppointmentStatus.ARRIVED);
@ -849,7 +849,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
siblingSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().setFamily("P2");
@ -886,7 +886,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
displaySp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(displaySp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
MedicationStatement ms1 = new MedicationStatement();
ms1.setMedication(new CodeableConcept());
@ -919,7 +919,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(sp));
mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Specimen specimen = new Specimen();
specimen.setId("#FOO");
@ -962,7 +962,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
IIdType spId = mySearchParameterDao.create(fooSp, mySrd).getId().toUnqualifiedVersionless();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.setGender(AdministrativeGender.MALE);
@ -993,7 +993,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
// Delete the param
mySearchParameterDao.delete(spId, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
myResourceReindexingSvc.forceReindexingPass();
myResourceReindexingSvc.forceReindexingPass();
@ -1021,7 +1021,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.DRAFT);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.setGender(AdministrativeGender.MALE);

View File

@ -13,6 +13,7 @@ import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.dstu3.model.*;
@ -40,7 +41,6 @@ import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.TestUtil;
@SuppressWarnings("unchecked")
public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
@ -667,6 +667,9 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
long betweenTime = System.currentTimeMillis();
TestUtil.sleepOneClick();
IIdType id2;
{
Patient patient = new Patient();
@ -1020,7 +1023,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
patient.addName().setFamily("testSearchLanguageParam").addGiven("Joe");
id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
Date betweenTime = new Date();
IIdType id2;
@ -1196,10 +1199,9 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
id0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
int sleep = 100;
TestUtil.sleepOneClick();
long start = System.currentTimeMillis();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(sleep);
TestUtil.sleepOneClick();
IIdType id1a;
{
@ -1218,7 +1220,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
ourLog.info("Res 2: {}", myPatientDao.read(id1a, mySrd).getMeta().getLastUpdatedElement().getValueAsString());
ourLog.info("Res 3: {}", myPatientDao.read(id1b, mySrd).getMeta().getLastUpdatedElement().getValueAsString());
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(sleep);
TestUtil.sleepOneClick();
long end = System.currentTimeMillis();
SearchParameterMap map;
@ -1615,18 +1617,18 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
obs01.setSubject(new Reference(patientId01));
IIdType obsId01 = myObservationDao.create(obs01, mySrd).getId().toUnqualifiedVersionless();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Date between = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Observation obs02 = new Observation();
obs02.setEffective(new DateTimeType(new Date()));
obs02.setSubject(new Reference(locId01));
IIdType obsId02 = myObservationDao.create(obs02, mySrd).getId().toUnqualifiedVersionless();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Date after = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
ourLog.info("P1[{}] L1[{}] Obs1[{}] Obs2[{}]", new Object[] { patientId01, locId01, obsId01, obsId02 });
@ -1853,15 +1855,15 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
patient.addName().setFamily("Tester_testSearchStringParam").addGiven("Joe");
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
Date between = new Date();
Thread.sleep(10);
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("002");
patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John");
pid2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
Thread.sleep(10);
TestUtil.sleepOneClick();
Date after = new Date();
SearchParameterMap params;
@ -2871,6 +2873,8 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
tag1id = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
Date betweenDate = new Date();
IIdType tag2id;
@ -3193,7 +3197,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
p01.addName().setFamily("B").addGiven("A");
String id1 = myPatientDao.create(p01).getId().toUnqualifiedVersionless().getValue();
Thread.sleep(10);
TestUtil.sleepOneClick();
// Numeric ID
Patient p02 = new Patient();
@ -3203,7 +3207,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
p02.addName().setFamily("Z").addGiven("Z");
String id2 = myPatientDao.create(p02).getId().toUnqualifiedVersionless().getValue();
Thread.sleep(10);
TestUtil.sleepOneClick();
// Forced ID
Patient pAB = new Patient();
@ -3213,7 +3217,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
pAB.addName().setFamily("A").addGiven("B");
myPatientDao.update(pAB);
Thread.sleep(10);
TestUtil.sleepOneClick();
// Forced ID
Patient pAA = new Patient();

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4SearchPageExpiryTest;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam;
@ -29,7 +30,6 @@ import org.springframework.transaction.support.TransactionTemplate;
import java.util.Date;
import java.util.concurrent.atomic.AtomicLong;
import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*;
@ -105,8 +105,7 @@ public class FhirResourceDaoDstu3SearchPageExpiryTest extends BaseJpaDstu3Test {
}
assertEquals(searchUuid1, searchUuid2);
sleepAtLeast(501);
TestUtil.sleepAtLeast(501);
// We're now past 500ms so we shouldn't reuse the search
final String searchUuid3;
@ -277,7 +276,7 @@ public class FhirResourceDaoDstu3SearchPageExpiryTest extends BaseJpaDstu3Test {
}
assertEquals(searchUuid1, searchUuid2);
sleepAtLeast(501);
TestUtil.sleepAtLeast(501);
// We're now past 500ms so we shouldn't reuse the search
@ -363,7 +362,7 @@ public class FhirResourceDaoDstu3SearchPageExpiryTest extends BaseJpaDstu3Test {
}
});
if (search == null) {
sleepAtLeast(100);
TestUtil.sleepAtLeast(100);
}
}
assertNotNull("Search " + bundleProvider.getUuid() + " not found on disk after 10 seconds", search);

View File

@ -2867,21 +2867,21 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
p.addName().setFamily(methodName);
IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
p = new Patient();
p.addIdentifier().setSystem("urn:system2").setValue(methodName);
p.addName().setFamily(methodName);
IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
p = new Patient();
p.addIdentifier().setSystem("urn:system3").setValue(methodName);
p.addName().setFamily(methodName);
IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
p = new Patient();
p.addIdentifier().setSystem("urn:system4").setValue(methodName);

View File

@ -2,11 +2,10 @@ package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.TokenParam;
@ -82,7 +81,7 @@ public class FhirResourceDaoDstu3UniqueSearchParamTest extends BaseJpaDstu3Test
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
private void createUniqueIndexCoverageBeneficiary() {
@ -121,7 +120,7 @@ public class FhirResourceDaoDstu3UniqueSearchParamTest extends BaseJpaDstu3Test
.setUrl(SearchParamConstants.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
private void createUniqueNameAndManagingOrganizationSps() {
@ -159,13 +158,13 @@ public class FhirResourceDaoDstu3UniqueSearchParamTest extends BaseJpaDstu3Test
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
@Test
public void testDetectUniqueSearchParams() {
createUniqueBirthdateAndGenderSps();
List<JpaRuntimeSearchParam> params = mySearchParamRegsitry.getActiveUniqueSearchParams("Patient");
List<JpaRuntimeSearchParam> params = mySearchParamRegistry.getActiveUniqueSearchParams("Patient");
assertEquals(1, params.size());
assertEquals(params.get(0).isUnique(), true);

View File

@ -306,7 +306,7 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test {
assertEquals("1", outcome.getId().getVersionIdPart());
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Date now = new Date();
Patient retrieved = myPatientDao.read(outcome.getId(), mySrd);
InstantType updated = retrieved.getMeta().getLastUpdatedElement().copy();

View File

@ -4,6 +4,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
@ -11,7 +12,6 @@ import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
@ -25,8 +25,6 @@ import org.junit.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast;
public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3ValidateTest.class);
@ -64,7 +62,7 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test {
MethodOutcome results = myQuestionnaireResponseDao.validate(qr, null, null, null, null, null, null);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(results.getOperationOutcome()));
sleepAtLeast(2500);
TestUtil.sleepAtLeast(2500);
try {
myQuestionnaireResponseDao.validate(qr, null, null, null, null, null, null);
fail();

View File

@ -8,13 +8,14 @@ import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorRegistry;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
@ -150,6 +151,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
protected IFhirResourceDao<RiskAssessment> myRiskAssessmentDao;
protected IServerInterceptor myInterceptor;
@Autowired
protected IInterceptorRegistry myInterceptorRegistry;
@Autowired
@Qualifier("myLocationDaoR4")
protected IFhirResourceDao<Location> myLocationDao;
@Autowired
@ -226,7 +229,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
@Qualifier("mySearchParameterDaoR4")
protected IFhirResourceDao<SearchParameter> mySearchParameterDao;
@Autowired
protected ISearchParamRegistry mySearchParamRegsitry;
protected BaseSearchParamRegistry mySearchParamRegistry;
@Autowired
protected IStaleSearchDeletingSvc myStaleSearchDeletingSvc;
@Autowired
@ -285,6 +288,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis());
myDaoConfig.setSuppressUpdatesWithNoChange(new DaoConfig().isSuppressUpdatesWithNoChange());
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
myInterceptorRegistry.clearAnonymousHookForUnitTest();
}
@After
@ -326,7 +331,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
@Transactional()
public void beforePurgeDatabase() throws InterruptedException {
final EntityManager entityManager = this.myEntityManager;
purgeDatabase(myDaoConfig, mySystemDao, myResourceReindexingSvc, mySearchCoordinatorSvc, mySearchParamRegsitry);
purgeDatabase(myDaoConfig, mySystemDao, myResourceReindexingSvc, mySearchCoordinatorSvc, mySearchParamRegistry);
}
@Before

View File

@ -2,8 +2,8 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
@ -163,7 +163,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
sp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
org.hl7.fhir.r4.model.Practitioner pract = new org.hl7.fhir.r4.model.Practitioner();
pract.setId("A");
@ -193,7 +193,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
eyeColourSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(eyeColourSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.setActive(true);
@ -215,7 +215,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
attendingSp.getTarget().add(new CodeType("Practitioner"));
IIdType spId = mySearchParameterDao.create(attendingSp, mySrd).getId().toUnqualifiedVersionless();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Practitioner p1 = new Practitioner();
p1.addName().setFamily("P1");
@ -265,7 +265,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
identifierSp.setStatus(Enumerations.PublicationStatus.RETIRED);
mySearchParameterDao.create(identifierSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p = new Patient();
p.addName().addGiven("G");
@ -305,7 +305,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
identifierSp.setStatus(Enumerations.PublicationStatus.RETIRED);
mySearchParameterDao.create(identifierSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p = new Patient();
p.addName().addGiven("G");
@ -342,7 +342,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
sp.addTarget("Condition");
sp.addTarget("Observation");
mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Condition condition = new Condition();
condition.getCode().setText("A condition");
@ -378,7 +378,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
sp.setType(Enumerations.SearchParamType.TOKEN);
sp.setExpression("MedicationRequest.reasonCode | ServiceRequest.reasonCode");
mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
MedicationRequest mr = new MedicationRequest();
mr.addReasonCode().addCoding().setSystem("foo").setCode("bar");
@ -425,7 +425,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
siblingSp.getTarget().add(new CodeType("Organization"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.addName().setFamily("P1");
@ -469,7 +469,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
siblingSp.getTarget().add(new CodeType("Patient"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.addName().setFamily("P1");
@ -523,7 +523,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.addName().setFamily("P1");
@ -577,7 +577,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
eyeColourSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(eyeColourSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.setActive(true);
@ -611,7 +611,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
siblingSp.getTarget().add(new CodeType("Organization"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().setFamily("P2");
@ -649,7 +649,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
siblingSp.getTarget().add(new CodeType("Organization"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().setFamily("P2");
@ -686,7 +686,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Appointment apt = new Appointment();
apt.setStatus(AppointmentStatus.ARRIVED);
@ -739,7 +739,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
txTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus theArg0) {
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
});
@ -783,7 +783,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().setFamily("P2");
@ -821,7 +821,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
siblingSp.getTarget().add(new CodeType("Appointment"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Appointment apt = new Appointment();
apt.setStatus(AppointmentStatus.ARRIVED);
@ -863,7 +863,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Appointment apt = new Appointment();
apt.setStatus(AppointmentStatus.ARRIVED);
@ -906,7 +906,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
siblingSp.getTarget().add(new CodeType("Observation"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Appointment apt = new Appointment();
apt.setStatus(AppointmentStatus.ARRIVED);
@ -948,7 +948,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
siblingSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient patient = new Patient();
patient.addName().setFamily("P2");
@ -986,7 +986,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
IIdType spId = mySearchParameterDao.create(fooSp, mySrd).getId().toUnqualifiedVersionless();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.addIdentifier().setSystem("FOO123").setValue("BAR678");
@ -1030,7 +1030,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
IIdType spId = mySearchParameterDao.create(fooSp, mySrd).getId().toUnqualifiedVersionless();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.addIdentifier().setSystem("http://AAA").setValue("BAR678");
@ -1074,7 +1074,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
sp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Specimen specimen = new Specimen();
specimen.setId("#FOO");
@ -1117,7 +1117,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
IIdType spId = mySearchParameterDao.create(fooSp, mySrd).getId().toUnqualifiedVersionless();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.setGender(AdministrativeGender.MALE);
@ -1148,7 +1148,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
// Delete the param
mySearchParameterDao.delete(spId, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
myResourceReindexingSvc.forceReindexingPass();
// Try with custom gender SP
@ -1175,7 +1175,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.DRAFT);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.setGender(AdministrativeGender.MALE);

View File

@ -1,9 +1,11 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.config.CaptureQueriesListener;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.parser.StrictErrorHandler;
@ -12,7 +14,7 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
@ -39,6 +41,7 @@ import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@ -53,6 +56,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis());
myDaoConfig.setFetchSizeDefaultMaximum(new DaoConfig().getFetchSizeDefaultMaximum());
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
}
@Before
@ -151,16 +155,16 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
List<String> ids;
Date beforeAll = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Organization org = new Organization();
org.setName("O1");
org.setId("O1");
myOrganizationDao.update(org);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Date beforePatient = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Patient p = new Patient();
p.setId("P1");
@ -168,7 +172,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
p.setManagingOrganization(new Reference("Organization/O1"));
myPatientDao.update(p);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Date afterAll = new Date();
// Search with between date (should still return Organization even though
@ -216,7 +220,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
myOrganizationDao.update(org);
Date beforeAll = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Patient p = new Patient();
p.setId("P1");
@ -224,17 +228,17 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
p.setManagingOrganization(new Reference("Organization/O1"));
myPatientDao.update(p);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Date beforeOrg = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
org = new Organization();
org.setActive(true);
org.setId("O1");
myOrganizationDao.update(org);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Date afterAll = new Date();
// Everything should come back
@ -614,7 +618,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
expect1.setResource(resource);
expect1.calculateHashes();
assertThat("Got: \"" + results.toString()+"\"", results, containsInAnyOrder(expect0, expect1));
assertThat("Got: \"" + results.toString() + "\"", results, containsInAnyOrder(expect0, expect1));
}
});
}
@ -889,7 +893,11 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
patient.addIdentifier().setSystem("urn:system").setValue("001");
id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
long betweenTime = System.currentTimeMillis();
IIdType id2;
{
Patient patient = new Patient();
@ -1056,7 +1064,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 150, "http://bar", "code1");
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true).add(param, v1);
IBundleProvider result = myObservationDao.search(map);
assertThat("Got: "+ toUnqualifiedVersionlessIdValues(result), toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id1.getValue()));
assertThat("Got: " + toUnqualifiedVersionlessIdValues(result), toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id1.getValue()));
}
}
@ -1088,7 +1096,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
CompositeParam<TokenParam, QuantityParam> val = new CompositeParam<>(v0, v1);
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true).add(param, val);
IBundleProvider result = myObservationDao.search(map);
assertThat("Got: "+ toUnqualifiedVersionlessIdValues(result), toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id2.getValue()));
assertThat("Got: " + toUnqualifiedVersionlessIdValues(result), toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id2.getValue()));
}
{
TokenParam v0 = new TokenParam("http://foo", "code1");
@ -1136,6 +1144,40 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
}
/**
* See #1174
*/
@Test
public void testSearchDateInSavedSearch() {
for (int i = 1; i <= 9; i++) {
Patient p1 = new Patient();
p1.getBirthDateElement().setValueAsString("1980-01-0" + i);
String id1 = myPatientDao.create(p1).getId().toUnqualifiedVersionless().getValue();
}
myDaoConfig.setSearchPreFetchThresholds(Lists.newArrayList(3, 6, 10));
{
// Don't load synchronous
SearchParameterMap map = new SearchParameterMap();
map.setLastUpdated(new DateRangeParam().setUpperBound(new DateParam(ParamPrefixEnum.LESSTHAN, "2022-01-01")));
IBundleProvider found = myPatientDao.search(map);
Set<String> dates = new HashSet<>();
for (int i = 0; i < 9; i++) {
Patient nextResource = (Patient) found.getResources(i, i + 1).get(0);
dates.add(nextResource.getBirthDateElement().getValueAsString());
}
assertThat(dates, hasItems(
"1980-01-01",
"1980-01-09"
));
assertFalse(map.isLoadSynchronous());
assertNull(map.getLoadSynchronousUpTo());
}
}
/**
* #222
*/
@ -1264,8 +1306,12 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
Date betweenTime = new Date();
TestUtil.sleepOneClick();
IIdType id2;
{
Patient patient = new Patient();
@ -1439,10 +1485,11 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
id0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
int sleep = 100;
TestUtil.sleepOneClick();
long start = System.currentTimeMillis();
Thread.sleep(sleep);
TestUtil.sleepOneClick();
IIdType id1a;
{
@ -1450,6 +1497,9 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
patient.addIdentifier().setSystem("urn:system").setValue("001");
id1a = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
IIdType id1b;
{
Patient patient = new Patient();
@ -1461,7 +1511,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
ourLog.info("Res 2: {}", myPatientDao.read(id1a, mySrd).getMeta().getLastUpdatedElement().getValueAsString());
ourLog.info("Res 3: {}", myPatientDao.read(id1b, mySrd).getMeta().getLastUpdatedElement().getValueAsString());
Thread.sleep(sleep);
TestUtil.sleepOneClick();
long end = System.currentTimeMillis();
SearchParameterMap map;
@ -1487,7 +1538,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
map = new SearchParameterMap();
map.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.GREATERTHAN, startDateTime.getValue()),
new DateParam(ParamPrefixEnum.LESSTHAN, myPatientDao.read(id1b, mySrd).getMeta().getLastUpdatedElement().getValue())));
new DateParam(ParamPrefixEnum.LESSTHAN, TestUtil.getTimestamp(myPatientDao.read(id1b, mySrd)))));
ourLog.info("Searching: {}", map.getLastUpdated());
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a));
}
@ -1857,15 +1908,15 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
obs01.setSubject(new Reference(patientId01));
IIdType obsId01 = myObservationDao.create(obs01, mySrd).getId().toUnqualifiedVersionless();
TestUtil.sleepOneClick();
Date between = new Date();
Thread.sleep(10);
Observation obs02 = new Observation();
obs02.setEffective(new DateTimeType(new Date()));
obs02.setSubject(new Reference(locId01));
IIdType obsId02 = myObservationDao.create(obs02, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(10);
TestUtil.sleepOneClick();
Date after = new Date();
ourLog.info("P1[{}] L1[{}] Obs1[{}] Obs2[{}]", patientId01, locId01, obsId01, obsId02);
@ -1988,15 +2039,16 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
patient.addName().setFamily("Tester_testSearchStringParam").addGiven("Joe");
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
Date between = new Date();
Thread.sleep(10);
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("002");
patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John");
pid2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
Thread.sleep(10);
TestUtil.sleepOneClick();
Date after = new Date();
SearchParameterMap params;
@ -2146,6 +2198,51 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
}
@Test
public void testSearchLinkToken() {
// /fhirapi/MedicationRequest?category=community&identifier=urn:oid:2.16.840.1.113883.3.7418.12.3%7C&intent=order&medication.code:text=calcitriol,hectorol,Zemplar,rocaltrol,vectical,vitamin%20D,doxercalciferol,paricalcitol&status=active,completed
Medication m = new Medication();
m.getCode().setText("valueb");
myMedicationDao.create(m);
MedicationRequest mr = new MedicationRequest();
mr.addCategory().addCoding().setCode("community");
mr.addIdentifier().setSystem("urn:oid:2.16.840.1.113883.3.7418.12.3").setValue("1");
mr.setIntent(MedicationRequest.MedicationRequestIntent.ORDER);
mr.setMedication(new Reference(m.getId()));
myMedicationRequestDao.create(mr);
SearchParameterMap sp = new SearchParameterMap();
sp.setLoadSynchronous(true);
sp.add("category", new TokenParam("community"));
sp.add("identifier", new TokenParam("urn:oid:2.16.840.1.113883.3.7418.12.3", "1"));
sp.add("intent", new TokenParam("order"));
ReferenceParam param1 = new ReferenceParam("valuea").setChain("code:text");
ReferenceParam param2 = new ReferenceParam("valueb").setChain("code:text");
ReferenceParam param3 = new ReferenceParam("valuec").setChain("code:text");
sp.add("medication", new ReferenceOrListParam().addOr(param1).addOr(param2).addOr(param3));
IBundleProvider retrieved = myMedicationRequestDao.search(sp);
assertEquals(1, retrieved.size().intValue());
List<String> queries = CaptureQueriesListener
.getLastNQueries()
.stream()
.filter(t -> t.getThreadName().equals("main"))
.filter(t -> t.getSql(false, false).toLowerCase().contains("select"))
.filter(t -> t.getSql(false, false).toLowerCase().contains("token"))
.map(t -> t.getSql(true, true))
.collect(Collectors.toList());
ourLog.info("Queries:\n {}", queries.stream().findFirst());
String searchQuery = queries.get(0);
assertEquals(searchQuery, 3, StringUtils.countMatches(searchQuery.toUpperCase(), "HFJ_SPIDX_TOKEN"));
assertEquals(searchQuery, 5, StringUtils.countMatches(searchQuery.toUpperCase(), "LEFT OUTER JOIN"));
}
@Test
public void testSearchTokenParam() {
Patient patient = new Patient();
@ -2966,6 +3063,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
tag1id = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
Date betweenDate = new Date();
IIdType tag2id;
@ -3298,7 +3397,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
"Observation/YES21",
"Observation/YES22",
"Observation/YES23"
));
));
}
private void createObservationWithEffective(String theId, String theEffective) {

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.parser.StrictErrorHandler;
@ -12,7 +13,6 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
@ -153,16 +153,16 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
List<String> ids;
Date beforeAll = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Organization org = new Organization();
org.setName("O1");
org.setId("O1");
myOrganizationDao.update(org);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Date beforePatient = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Patient p = new Patient();
p.setId("P1");
@ -170,7 +170,7 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
p.setManagingOrganization(new Reference("Organization/O1"));
myPatientDao.update(p);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Date afterAll = new Date();
// Search with between date (should still return Organization even though
@ -218,7 +218,7 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
myOrganizationDao.update(org);
Date beforeAll = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Patient p = new Patient();
p.setId("P1");
@ -226,17 +226,17 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
p.setManagingOrganization(new Reference("Organization/O1"));
myPatientDao.update(p);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Date beforeOrg = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
org = new Organization();
org.setActive(true);
org.setId("O1");
myOrganizationDao.update(org);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
Date afterAll = new Date();
// Everything should come back
@ -891,6 +891,7 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
patient.addIdentifier().setSystem("urn:system").setValue("001");
id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
long betweenTime = System.currentTimeMillis();
IIdType id2;
{
@ -1266,6 +1267,8 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
Date betweenTime = new Date();
IIdType id2;
@ -1349,8 +1352,7 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
public void testSearchLastUpdatedParam() throws InterruptedException {
String methodName = "testSearchLastUpdatedParam";
int sleep = 100;
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(sleep);
TestUtil.sleepOneClick();
DateTimeType beforeAny = new DateTimeType(new Date(), TemporalPrecisionEnum.MILLI);
IIdType id1a;
@ -1368,9 +1370,9 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
id1b = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1100);
TestUtil.sleepAtLeast(1100);
DateTimeType beforeR2 = new DateTimeType(new Date(), TemporalPrecisionEnum.MILLI);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1100);
TestUtil.sleepAtLeast(1100);
IIdType id2;
{
@ -1441,10 +1443,11 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
id0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
int sleep = 100;
TestUtil.sleepOneClick();
long start = System.currentTimeMillis();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(sleep);
TestUtil.sleepOneClick();
IIdType id1a;
{
@ -1452,6 +1455,9 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
patient.addIdentifier().setSystem("urn:system").setValue("001");
id1a = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
IIdType id1b;
{
Patient patient = new Patient();
@ -1463,11 +1469,13 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
ourLog.info("Res 2: {}", myPatientDao.read(id1a, mySrd).getMeta().getLastUpdatedElement().getValueAsString());
ourLog.info("Res 3: {}", myPatientDao.read(id1b, mySrd).getMeta().getLastUpdatedElement().getValueAsString());
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(sleep);
TestUtil.sleepOneClick();
long end = System.currentTimeMillis();
SearchParameterMap map;
Date startDate = new Date(start);
TestUtil.sleepOneClick();
Date endDate = new Date(end);
DateTimeType startDateTime = new DateTimeType(startDate, TemporalPrecisionEnum.MILLI);
DateTimeType endDateTime = new DateTimeType(endDate, TemporalPrecisionEnum.MILLI);
@ -1489,7 +1497,7 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
map = new SearchParameterMap();
map.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.GREATERTHAN, startDateTime.getValue()),
new DateParam(ParamPrefixEnum.LESSTHAN, myPatientDao.read(id1b, mySrd).getMeta().getLastUpdatedElement().getValue())));
new DateParam(ParamPrefixEnum.LESSTHAN, TestUtil.getTimestamp(myPatientDao.read(id1b, mySrd)))));
ourLog.info("Searching: {}", map.getLastUpdated());
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a));
}
@ -1860,14 +1868,14 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
IIdType obsId01 = myObservationDao.create(obs01, mySrd).getId().toUnqualifiedVersionless();
Date between = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(10);
TestUtil.sleepOneClick();
Observation obs02 = new Observation();
obs02.setEffective(new DateTimeType(new Date()));
obs02.setSubject(new Reference(locId01));
IIdType obsId02 = myObservationDao.create(obs02, mySrd).getId().toUnqualifiedVersionless();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(10);
TestUtil.sleepOneClick();
Date after = new Date();
ourLog.info("P1[{}] L1[{}] Obs1[{}] Obs2[{}]", patientId01, locId01, obsId01, obsId02);
@ -1990,15 +1998,16 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
patient.addName().setFamily("Tester_testSearchStringParam").addGiven("Joe");
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
Date between = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(10);
TestUtil.sleepOneClick();
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("002");
patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John");
pid2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(10);
TestUtil.sleepOneClick();
Date after = new Date();
SearchParameterMap params;
@ -2945,6 +2954,8 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
tag1id = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
}
TestUtil.sleepOneClick();
Date betweenDate = new Date();
IIdType tag2id;

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchStatusEnum;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam;
@ -29,7 +30,6 @@ import javax.annotation.Nullable;
import java.util.Date;
import java.util.concurrent.atomic.AtomicLong;
import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*;
@ -100,7 +100,7 @@ public class FhirResourceDaoR4SearchPageExpiryTest extends BaseJpaR4Test {
}
assertEquals(searchUuid1, searchUuid2);
sleepAtLeast(501);
TestUtil.sleepAtLeast(501);
// We're now past 500ms so we shouldn't reuse the search
@ -274,7 +274,7 @@ public class FhirResourceDaoR4SearchPageExpiryTest extends BaseJpaR4Test {
}
assertEquals(searchUuid1, searchUuid2);
sleepAtLeast(501);
TestUtil.sleepAtLeast(501);
// We're now past 500ms so we shouldn't reuse the search
@ -360,7 +360,7 @@ public class FhirResourceDaoR4SearchPageExpiryTest extends BaseJpaR4Test {
}
});
if (search == null) {
sleepAtLeast(100);
TestUtil.sleepAtLeast(100);
}
}
assertNotNull("Search " + bundleProvider.getUuid() + " not found on disk after 10 seconds", search);
@ -407,7 +407,7 @@ public class FhirResourceDaoR4SearchPageExpiryTest extends BaseJpaR4Test {
for (int i = 0; i < 20 && search == null; i++) {
search = theSearchEntityDao.findByUuid(theUuid);
if (search == null || search.getStatus() == SearchStatusEnum.LOADING) {
sleepAtLeast(100);
TestUtil.sleepAtLeast(100);
}
}
assertNotNull(search);

View File

@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
@ -20,7 +21,6 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
@ -3159,16 +3159,22 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
p.addName().setFamily(methodName);
IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
TestUtil.sleepOneClick();
p = new Patient();
p.addIdentifier().setSystem("urn:system2").setValue(methodName);
p.addName().setFamily(methodName);
IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
TestUtil.sleepOneClick();
p = new Patient();
p.addIdentifier().setSystem("urn:system3").setValue(methodName);
p.addName().setFamily(methodName);
IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
TestUtil.sleepOneClick();
p = new Patient();
p.addIdentifier().setSystem("urn:system4").setValue(methodName);
p.addName().setFamily(methodName);

View File

@ -8,7 +8,6 @@ import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.TokenParam;
@ -97,7 +96,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
SearchBuilder.resetLastHandlerMechanismForUnitTest();
}
@ -139,7 +138,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
.setUrl(SearchParamConstants.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
@ -168,7 +167,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
.setUrl(SearchParamConstants.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
@ -197,7 +196,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
.setUrl(SearchParamConstants.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
@ -226,7 +225,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
.setUrl(SearchParamConstants.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
private void createUniqueNameAndManagingOrganizationSps() {
@ -264,7 +263,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
private void createUniqueObservationSubjectDateCode() {
@ -316,13 +315,13 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
@Test
public void testDetectUniqueSearchParams() {
createUniqueBirthdateAndGenderSps();
List<JpaRuntimeSearchParam> params = mySearchParamRegsitry.getActiveUniqueSearchParams("Patient");
List<JpaRuntimeSearchParam> params = mySearchParamRegistry.getActiveUniqueSearchParams("Patient");
assertEquals(1, params.size());
assertTrue(params.get(0).isUnique());
@ -474,7 +473,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
myResourceIndexedCompositeStringUniqueDao.deleteAll();
assertEquals(1, mySearchParamRegsitry.getActiveUniqueSearchParams("Observation").size());
assertEquals(1, mySearchParamRegistry.getActiveUniqueSearchParams("Observation").size());
myResourceReindexingSvc.markAllResourcesForReindexing();
myResourceReindexingSvc.forceReindexingPass();

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
@ -12,7 +13,6 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
@ -313,12 +313,15 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
assertEquals("1", outcome.getId().getVersionIdPart());
TestUtil.sleepOneClick();
Date now = new Date();
Patient retrieved = myPatientDao.read(outcome.getId(), mySrd);
InstantType updated = retrieved.getMeta().getLastUpdatedElement().copy();
InstantType updated = TestUtil.getTimestamp(retrieved);
assertTrue(updated.before(now));
Thread.sleep(1000);
TestUtil.sleepOneClick();
reset(myInterceptor);
retrieved.getIdentifier().get(0).setValue("002");
@ -335,17 +338,18 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
assertEquals("Patient", details.getResourceType());
assertEquals(Patient.class, details.getResource().getClass());
TestUtil.sleepOneClick();
Date now2 = new Date();
Patient retrieved2 = myPatientDao.read(outcome.getId().toVersionless(), mySrd);
assertEquals("2", retrieved2.getIdElement().getVersionIdPart());
assertEquals("002", retrieved2.getIdentifier().get(0).getValue());
InstantType updated2 = retrieved2.getMeta().getLastUpdatedElement();
InstantType updated2 = TestUtil.getTimestamp(retrieved2);
assertTrue(updated2.after(now));
assertTrue(updated2.before(now2));
Thread.sleep(2000);
TestUtil.sleepOneClick();
/*
* Get history

View File

@ -7,7 +7,6 @@ import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport;
@ -71,11 +70,6 @@ public class SearchParamExtractorR4Test {
throw new UnsupportedOperationException();
}
@Override
public void refreshCacheIfNecessary() {
// nothing
}
@Override
public void requestRefresh() {
// nothing
@ -90,11 +84,6 @@ public class SearchParamExtractorR4Test {
public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
return null;
}
@Override
public void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider) {
// nothing
}
};
}

View File

@ -211,9 +211,11 @@ public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL");
myObservationDao.update(obs, mySrd);
// Try to wait for the indexing to complete
waitForSize(2, ()-> fetchSuggestionCount(ptId));
HttpGet get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + ptId.getIdPart() + "/$everything&searchParam=_content&text=zxc&_pretty=true&_format=xml");
CloseableHttpResponse http = ourHttpClient.execute(get);
try {
try (CloseableHttpResponse http = ourHttpClient.execute(get)) {
assertEquals(200, http.getStatusLine().getStatusCode());
String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(output);
@ -225,8 +227,16 @@ public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
assertEquals("score", parameters.getParameter().get(0).getPart().get(1).getName());
assertEquals(new DecimalDt("1.0"), parameters.getParameter().get(0).getPart().get(1).getValue());
} finally {
http.close();
}
}
private Number fetchSuggestionCount(IIdType thePtId) throws IOException {
HttpGet get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + thePtId.getIdPart() + "/$everything&searchParam=_content&text=zxc&_pretty=true&_format=xml");
try (CloseableHttpResponse http = ourHttpClient.execute(get)) {
assertEquals(200, http.getStatusLine().getStatusCode());
String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
Parameters parameters = ourCtx.newXmlParser().parseResource(Parameters.class, output);
return parameters.getParameter().size();
}
}

View File

@ -1,37 +1,43 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.*;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.CapabilityStatement.*;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.gclient.ReferenceClientParam;
import ca.uhn.fhir.rest.gclient.TokenClientParam;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestComponent;
import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProviderDstu3Test {
@ -56,7 +62,7 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv
super.beforeResetConfig();
myModelConfig.setDefaultSearchParamsCanBeOverridden(new ModelConfig().isDefaultSearchParamsCanBeOverridden());
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
private Map<String, CapabilityStatementRestResourceSearchParamComponent> extractSearchParams(CapabilityStatement conformance, String resType) {
@ -142,7 +148,7 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv
txTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus theStatus) {
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
});
@ -198,7 +204,7 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.RETIRED);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
conformance = ourClient
.fetchConformance()
@ -259,7 +265,7 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv
attendingSp.getTarget().add(new CodeType("Practitioner"));
IIdType spId = mySearchParameterDao.create(attendingSp, mySrd).getId().toUnqualifiedVersionless();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Practitioner p1 = new Practitioner();
p1.addName().setFamily("P1");
@ -309,7 +315,7 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv
.resource(eyeColourSp)
.execute();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.setActive(true);
@ -351,7 +357,7 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.setGender(AdministrativeGender.MALE);
@ -395,7 +401,7 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.setGender(AdministrativeGender.MALE);

View File

@ -4,6 +4,8 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
@ -28,6 +30,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.hamcrest.Matchers.in;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.*;
@ -148,6 +151,58 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource
}
@Test
public void testReadInTransaction() {
Patient patient = new Patient();
patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100");
patient.addName().setFamily("Tester").addGiven("Raghad");
IIdType id = ourClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|100").execute().getId().toUnqualifiedVersionless();
ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
String authHeader = theRequestDetails.getHeader("Authorization");
if (!"Bearer AAA".equals(authHeader)) {
throw new AuthenticationException("Invalid auth header: " + authHeader);
}
return new RuleBuilder()
.allow().transaction().withAnyOperation().andApplyNormalRules().andThen()
.allow().read().resourcesOfType(Patient.class).withAnyId()
.build();
}
});
SimpleRequestHeaderInterceptor interceptor = new SimpleRequestHeaderInterceptor("Authorization", "Bearer AAA");
try {
ourClient.registerInterceptor(interceptor);
Bundle bundle;
Bundle responseBundle;
// Read
bundle = new Bundle();
bundle.setType(Bundle.BundleType.TRANSACTION);
bundle.addEntry().getRequest().setMethod(Bundle.HTTPVerb.GET).setUrl(id.getValue());
responseBundle = ourClient.transaction().withBundle(bundle).execute();
patient = (Patient) responseBundle.getEntry().get(0).getResource();
assertEquals("Tester", patient.getNameFirstRep().getFamily());
// Search
bundle = new Bundle();
bundle.setType(Bundle.BundleType.TRANSACTION);
bundle.addEntry().getRequest().setMethod(Bundle.HTTPVerb.GET).setUrl("Patient?");
responseBundle = ourClient.transaction().withBundle(bundle).execute();
responseBundle = (Bundle) responseBundle.getEntry().get(0).getResource();
patient = (Patient) responseBundle.getEntry().get(0).getResource();
assertEquals("Tester", patient.getNameFirstRep().getFamily());
} finally {
ourClient.unregisterInterceptor(interceptor);
}
}
/**
* See #751
*/

View File

@ -59,14 +59,14 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
protected static RestfulServer ourRestServer;
protected static String ourServerBase;
protected static SearchParamRegistryR4 ourSearchParamRegistry;
protected static DatabaseBackedPagingProvider ourPagingProvider;
private static DatabaseBackedPagingProvider ourPagingProvider;
protected static ISearchDao mySearchEntityDao;
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
protected static GenericWebApplicationContext ourWebApplicationContext;
protected static SubscriptionMatcherInterceptor ourSubscriptionMatcherInterceptor;
private static GenericWebApplicationContext ourWebApplicationContext;
private static SubscriptionMatcherInterceptor ourSubscriptionMatcherInterceptor;
private static Server ourServer;
protected IGenericClient ourClient;
protected ResourceCountCache ourResourceCountsCache;
ResourceCountCache ourResourceCountsCache;
private TerminologyUploaderProviderR4 myTerminologyUploaderProvider;
private Object ourGraphQLProvider;
private boolean ourRestHookSubscriptionInterceptorRequested;
@ -162,6 +162,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
mySearchEntityDao = wac.getBean(ISearchDao.class);
ourSearchParamRegistry = wac.getBean(SearchParamRegistryR4.class);
ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class);
ourSubscriptionMatcherInterceptor.start();
myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000);
@ -204,7 +205,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
fail("Failed to init subscriptions");
}
try {
mySubscriptionLoader.initSubscriptions();
mySubscriptionLoader.syncSubscriptions();
break;
} catch (ResourceVersionConflictException e) {
Thread.sleep(250);

View File

@ -0,0 +1,110 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.AfterClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class HookInterceptorR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HookInterceptorR4Test.class);
@Test
public void testOP_PRESTORAGE_RESOURCE_CREATED_ModifyResource() {
myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.OP_PRESTORAGE_RESOURCE_CREATED, t->{
Patient contents = (Patient) t.get(IBaseResource.class, 0);
contents.getNameFirstRep().setFamily("NEWFAMILY");
});
Patient p = new Patient();
p.getNameFirstRep().setFamily("OLDFAMILY");
MethodOutcome outcome = ourClient.create().resource(p).execute();
// Response reflects change, stored resource also does
Patient responsePatient = (Patient) outcome.getResource();
assertEquals("NEWFAMILY", responsePatient.getNameFirstRep().getFamily());
responsePatient = ourClient.read().resource(Patient.class).withId(outcome.getId()).execute();
assertEquals("NEWFAMILY", responsePatient.getNameFirstRep().getFamily());
}
@Test
public void testOP_PRECOMMIT_RESOURCE_CREATED_ModifyResource() {
myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED, t->{
Patient contents = (Patient) t.get(IBaseResource.class, 0);
contents.getNameFirstRep().setFamily("NEWFAMILY");
});
Patient p = new Patient();
p.getNameFirstRep().setFamily("OLDFAMILY");
MethodOutcome outcome = ourClient.create().resource(p).execute();
// Response reflects change, stored resource does not
Patient responsePatient = (Patient) outcome.getResource();
assertEquals("NEWFAMILY", responsePatient.getNameFirstRep().getFamily());
responsePatient = ourClient.read().resource(Patient.class).withId(outcome.getId()).execute();
assertEquals("OLDFAMILY", responsePatient.getNameFirstRep().getFamily());
}
@Test
public void testOP_PRESTORAGE_RESOURCE_UPDATED_ModifyResource() {
Patient p = new Patient();
p.setActive(true);
IIdType id = ourClient.create().resource(p).execute().getId();
myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.OP_PRESTORAGE_RESOURCE_UPDATED, t->{
Patient contents = (Patient) t.get(IBaseResource.class, 1);
contents.getNameFirstRep().setFamily("NEWFAMILY");
});
p = new Patient();
p.setId(id);
p.getNameFirstRep().setFamily("OLDFAMILY");
MethodOutcome outcome = ourClient.update().resource(p).execute();
// Response reflects change, stored resource also does
Patient responsePatient = (Patient) outcome.getResource();
assertEquals("NEWFAMILY", responsePatient.getNameFirstRep().getFamily());
responsePatient = ourClient.read().resource(Patient.class).withId(outcome.getId()).execute();
assertEquals("NEWFAMILY", responsePatient.getNameFirstRep().getFamily());
}
@Test
public void testOP_PRECOMMIT_RESOURCE_UPDATED_ModifyResource() {
Patient p = new Patient();
p.setActive(true);
IIdType id = ourClient.create().resource(p).execute().getId();
myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.OP_PRECOMMIT_RESOURCE_UPDATED, t->{
Patient contents = (Patient) t.get(IBaseResource.class, 1);
contents.getNameFirstRep().setFamily("NEWFAMILY");
});
p = new Patient();
p.setId(id);
p.getNameFirstRep().setFamily("OLDFAMILY");
MethodOutcome outcome = ourClient.update().resource(p).execute();
// Response reflects change, stored resource does not
Patient responsePatient = (Patient) outcome.getResource();
assertEquals("NEWFAMILY", responsePatient.getNameFirstRep().getFamily());
responsePatient = ourClient.read().resource(Patient.class).withId(outcome.getId()).execute();
assertEquals("OLDFAMILY", responsePatient.getNameFirstRep().getFamily());
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -1,37 +1,43 @@
package ca.uhn.fhir.jpa.provider.r4;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.*;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.CapabilityStatement.*;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
import org.hl7.fhir.r4.model.SearchParameter.XPathUsageType;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.gclient.ReferenceClientParam;
import ca.uhn.fhir.rest.gclient.TokenClientParam;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestComponent;
import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
import org.hl7.fhir.r4.model.SearchParameter.XPathUsageType;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProviderR4Test {
@ -56,7 +62,7 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide
super.beforeResetConfig();
myModelConfig.setDefaultSearchParamsCanBeOverridden(new ModelConfig().isDefaultSearchParamsCanBeOverridden());
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
private Map<String, CapabilityStatementRestResourceSearchParamComponent> extractSearchParams(CapabilityStatement conformance, String resType) {
@ -142,7 +148,7 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide
txTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus theStatus) {
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
}
});
@ -198,7 +204,7 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.RETIRED);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
conformance = ourClient
.fetchConformance()
@ -260,7 +266,7 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide
attendingSp.getTarget().add(new CodeType("Practitioner"));
IIdType spId = mySearchParameterDao.create(attendingSp, mySrd).getId().toUnqualifiedVersionless();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Practitioner p1 = new Practitioner();
p1.addName().setFamily("P1");
@ -310,7 +316,7 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide
.resource(eyeColourSp)
.execute();
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient p1 = new Patient();
p1.setActive(true);
@ -352,7 +358,7 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.setGender(AdministrativeGender.MALE);
@ -396,7 +402,7 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
Patient pat = new Patient();
pat.setGender(AdministrativeGender.MALE);

View File

@ -366,6 +366,7 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
}
private void transaction(Bundle theBundle) throws IOException {
String resource = myFhirCtx.newXmlParser().encodeResourceToString(theBundle);
HttpPost post = new HttpPost(ourServerBase + "/");

View File

@ -2,12 +2,12 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Patient;
import org.junit.After;
@ -160,12 +160,18 @@ public class ResourceProviderR4CacheTest extends BaseResourceProviderR4Test {
Date beforeFirst = new Date();
TestUtil.sleepOneClick();
Bundle results1 = ourClient.search().forResource("Patient").where(Patient.FAMILY.matches().value("FAM")).returnBundle(Bundle.class).execute();
TestUtil.sleepOneClick();
assertEquals(1, results1.getEntry().size());
assertEquals(1, mySearchEntityDao.count());
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), empty());
assertThat(results1.getMeta().getLastUpdated(), greaterThan(beforeFirst));
assertThat(results1.getMeta().getLastUpdated(), lessThan(new Date()));
Date results1Date = TestUtil.getTimestamp(results1).getValue();
assertThat(results1Date, greaterThan(beforeFirst));
assertThat(results1Date, lessThan(new Date()));
assertThat(results1.getId(), not(blankOrNullString()));
Patient pt2 = new Patient();

View File

@ -37,6 +37,7 @@ import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@ -116,13 +117,13 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
@SuppressWarnings("Duplicates")
public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4Test.class);
public static final int LARGE_NUMBER = 77;
private SearchCoordinatorSvcImpl mySearchCoordinatorSvcRaw;
private CapturingInterceptor myCapturingInterceptor = new CapturingInterceptor();
@ -198,6 +199,34 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
@Test
public void testSearchFetchPageBeyondEnd() {
for (int i = 0; i < 10; i++) {
Organization o = new Organization();
o.setId("O" + i);
o.setName("O" + i);
IIdType oid = ourClient.update().resource(o).execute().getId().toUnqualifiedVersionless();
}
Bundle output = ourClient
.search()
.forResource("Organization")
.count(3)
.returnBundle(Bundle.class)
.execute();
String nextPageUrl = output.getLink("next").getUrl();
String url = nextPageUrl.replace("_getpagesoffset=3", "_getpagesoffset=999");
ourLog.info("Going to request URL: {}", url);
output = ourClient
.loadPage()
.byUrl(url)
.andReturnBundle(Bundle.class)
.execute();
assertEquals(0, output.getEntry().size());
}
@Test
public void testDeleteConditional() {
@ -945,6 +974,19 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
}
@Test
@Ignore
public void testQuery() throws IOException {
ourLog.info("** Performing Search");
HttpGet read = new HttpGet(ourServerBase + "/MedicationRequest?category=community&identifier=urn:oid:2.16.840.1.113883.3.7418.12.3%7C&intent=order&medication.code:text=calcitriol,hectorol,Zemplar,rocaltrol,vectical,vitamin%20D,doxercalciferol,paricalcitol&status=active,completed");
try (CloseableHttpResponse response = ourHttpClient.execute(read)) {
ourLog.info(response.toString());
}
ourLog.info("** DONE Performing Search");
}
@Test
public void testDeleteResourceConditional1() throws IOException {
String methodName = "testDeleteResourceConditional1";
@ -1714,7 +1756,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
p.setActive(true);
IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
for (int i = 1; i < 77; i++) {
for (int i = 1; i < LARGE_NUMBER; i++) {
Observation obs = new Observation();
obs.setId("A" + StringUtils.leftPad(Integer.toString(i), 2, '0'));
obs.setSubject(new Reference(id));
@ -1752,8 +1794,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
assertThat(ids, hasItem(id.getIdPart()));
assertEquals(77, ids.size());
for (int i = 1; i < 77; i++) {
assertEquals(LARGE_NUMBER, ids.size());
for (int i = 1; i < LARGE_NUMBER; i++) {
assertThat(ids.size() + " ids: " + ids, ids, hasItem("A" + StringUtils.leftPad(Integer.toString(i), 2, '0')));
}
}
@ -3646,6 +3688,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
Search search1 = newTxTemplate().execute(theStatus -> mySearchEntityDao.findByUuid(uuid1));
Date lastReturned1 = search1.getSearchLastReturned();
TestUtil.sleepOneClick();
Bundle result2 = ourClient
.search()
.forResource("Organization")

View File

@ -279,6 +279,9 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL");
myObservationDao.update(obs, mySrd);
// Try to wait for the indexing to complete
waitForSize(2, ()-> fetchSuggestionCount(ptId));
HttpGet get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + ptId.getIdPart() + "/$everything&searchParam=_content&text=zxc&_pretty=true&_format=xml");
CloseableHttpResponse http = ourHttpClient.execute(get);
try {
@ -298,6 +301,16 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
}
}
private Number fetchSuggestionCount(IIdType thePtId) throws IOException {
HttpGet get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + thePtId.getIdPart() + "/$everything&searchParam=_content&text=zxc&_pretty=true&_format=xml");
try (CloseableHttpResponse http = ourHttpClient.execute(get)) {
assertEquals(200, http.getStatusLine().getStatusCode());
String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
Parameters parameters = ourCtx.newXmlParser().parseResource(Parameters.class, output);
return parameters.getParameter().size();
}
}
@Test
public void testSuggestKeywordsInvalid() throws Exception {
Patient patient = new Patient();

View File

@ -89,16 +89,22 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
ourHeaders.clear();
// Delete all Subscriptions
Bundle allSubscriptions = ourClient.search().forResource(Subscription.class).returnBundle(Bundle.class).execute();
for (IBaseResource next : BundleUtil.toListOfResources(myFhirCtx, allSubscriptions)) {
ourClient.delete().resource(next).execute();
if (ourClient != null) {
Bundle allSubscriptions = ourClient.search().forResource(Subscription.class).returnBundle(Bundle.class).execute();
for (IBaseResource next : BundleUtil.toListOfResources(myFhirCtx, allSubscriptions)) {
ourClient.delete().resource(next).execute();
}
waitForActivatedSubscriptionCount(0);
}
waitForActivatedSubscriptionCount(0);
LinkedBlockingQueueSubscribableChannel processingChannel = mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest();
processingChannel.clearInterceptorsForUnitTest();
if (processingChannel != null) {
processingChannel.clearInterceptorsForUnitTest();
}
myCountingInterceptor = new CountingInterceptor();
processingChannel.addInterceptorForUnitTest(myCountingInterceptor);
if (processingChannel != null) {
processingChannel.addInterceptorForUnitTest(myCountingInterceptor);
}
}
@ -147,9 +153,8 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
observation.setStatus(Observation.ObservationStatus.FINAL);
MethodOutcome methodOutcome = ourClient.create().resource(observation).execute();
observation.setId(methodOutcome.getId());
IIdType id = myObservationDao.create(observation).getId();
observation.setId(id);
return observation;
}

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.jpa.subscription;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSearchParamProvider;
import ca.uhn.fhir.rest.api.MethodOutcome;
import org.hl7.fhir.r4.model.Coding;
@ -17,8 +16,6 @@ import static org.junit.Assert.assertEquals;
public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test {
@Autowired
ISearchParamRegistry mySearchParamRegistry;
@Autowired
ISearchParamProvider origSearchParamProvider;
@ -44,7 +41,7 @@ public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test {
sp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
createSubscription(criteria, "application/json");
waitForActivatedSubscriptionCount(1);

View File

@ -0,0 +1,39 @@
package ca.uhn.fhir.jpa.subscription;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
/**
* Receives subscription notification without payloads.
*/
public class NotificationServlet extends HttpServlet {
private static final long serialVersionUID = 5957950857980374719L;
private final AtomicLong receivedNotificationCount = new AtomicLong();
private final List<String> receivedAuthorizationHeaders = Collections.synchronizedList(new ArrayList<>());
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
receivedNotificationCount.incrementAndGet();
receivedAuthorizationHeaders.add(req.getHeader("Authorization"));
}
public long getReceivedNotificationCount() {
return receivedNotificationCount.get();
}
public List<String> getReceivedAuthorizationHeaders() {
return receivedAuthorizationHeaders;
}
public void reset() {
receivedNotificationCount.set(0);
receivedAuthorizationHeaders.clear();
}
}

View File

@ -4,9 +4,9 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.TestR4Config;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.lang3.StringUtils;
@ -35,28 +35,41 @@ public class InMemorySubscriptionMatcherTestR4 {
@Autowired
InMemorySubscriptionMatcher myInMemorySubscriptionMatcher;
@Autowired
SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator;
@Autowired
FhirContext myContext;
private SubscriptionMatchResult match(IBaseResource resource, SearchParameterMap params) {
String criteria = params.toNormalizedQueryString(myContext);
ourLog.info("Criteria: <{}>", criteria);
return myInMemorySubscriptionMatcher.match(criteria, resource);
}
private void assertUnsupported(IBaseResource resource, SearchParameterMap params) {
assertFalse(match(resource, params).supported());
}
private void assertMatched(IBaseResource resource, SearchParameterMap params) {
private void assertMatched(Resource resource, SearchParameterMap params) {
SubscriptionMatchResult result = match(resource, params);
assertTrue(result.getUnsupportedReason(), result.supported());
assertTrue(result.matched());
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY, mySubscriptionStrategyEvaluator.determineStrategy(getCriteria(resource, params)));
}
private void assertNotMatched(IBaseResource resource, SearchParameterMap params) {
private void assertNotMatched(Resource resource, SearchParameterMap params) {
SubscriptionMatchResult result = match(resource, params);
assertTrue(result.getUnsupportedReason(), result.supported());
assertFalse(result.matched());
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY, mySubscriptionStrategyEvaluator.determineStrategy(getCriteria(resource, params)));
}
private SubscriptionMatchResult match(Resource theResource, SearchParameterMap theParams) {
return match(getCriteria(theResource, theParams), theResource);
}
private String getCriteria(Resource theResource, SearchParameterMap theParams) {
return theResource.getResourceType().name() + theParams.toNormalizedQueryString(myContext);
}
private SubscriptionMatchResult match(String criteria, Resource theResource) {
ourLog.info("Criteria: <{}>", criteria);
return myInMemorySubscriptionMatcher.match(criteria, theResource);
}
private void assertUnsupported(Resource resource, SearchParameterMap theParams) {
SubscriptionMatchResult result = match(resource, theParams);
assertFalse(result.supported());
assertEquals(SubscriptionMatchingStrategy.DATABASE, mySubscriptionStrategyEvaluator.determineStrategy(getCriteria(resource, theParams)));
}
/*
@ -92,7 +105,6 @@ public class InMemorySubscriptionMatcherTestR4 {
SearchParameterMap params = new SearchParameterMap();
params.add("_has", new HasParam("Observation", "subject", "identifier", "urn:system|FOO"));
String criteria = params.toNormalizedQueryString(myContext);
assertUnsupported(patient, params);
}
@ -129,7 +141,7 @@ public class InMemorySubscriptionMatcherTestR4 {
TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamN01");
StringParam v1 = new StringParam("testSearchCompositeParamS01");
CompositeParam<TokenParam, StringParam> val = new CompositeParam<TokenParam, StringParam>(v0, v1);
CompositeParam<TokenParam, StringParam> val = new CompositeParam<>(v0, v1);
SearchParameterMap params = new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_CODE_VALUE_STRING, val);
assertUnsupported(o1, params);
}
@ -169,11 +181,11 @@ public class InMemorySubscriptionMatcherTestR4 {
}
@Test
public void testIdNotSupported() {
public void testIdSupported() {
Observation o1 = new Observation();
SearchParameterMap params = new SearchParameterMap();
params.add("_id", new StringParam("testSearchForUnknownAlphanumericId"));
assertUnsupported(o1, params);
assertNotMatched(o1, params);
}
@Test
@ -189,7 +201,7 @@ public class InMemorySubscriptionMatcherTestR4 {
}
@Test
public void testSearchLastUpdatedParamUnsupported() throws InterruptedException {
public void testSearchLastUpdatedParamUnsupported() {
String methodName = "testSearchLastUpdatedParam";
DateTimeType today = new DateTimeType(new Date(), TemporalPrecisionEnum.DAY);
Patient patient = new Patient();
@ -293,12 +305,12 @@ public class InMemorySubscriptionMatcherTestR4 {
@Test
public void testSearchQuantityWrongParam() {
Condition c1 = new Condition();
c1.setAbatement(new Range().setLow((SimpleQuantity) new SimpleQuantity().setValue(1L)).setHigh((SimpleQuantity) new SimpleQuantity().setValue(1L)));
c1.setAbatement(new Range().setLow(new SimpleQuantity().setValue(1L)).setHigh(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)));
c2.setOnset(new Range().setLow(new SimpleQuantity().setValue(1L)).setHigh(new SimpleQuantity().setValue(1L)));
params = new SearchParameterMap().add(Condition.SP_ONSET_AGE, new QuantityParam("1"));
assertMatched(c2, params);
@ -380,13 +392,16 @@ public class InMemorySubscriptionMatcherTestR4 {
params.add(Patient.SP_FAMILY, new StringParam("testSearchNameParam01Fam"));
try {
String criteria = params.toNormalizedQueryString(myContext);
CanonicalSubscription subscription = new CanonicalSubscription();
subscription.setCriteriaString(criteria);
subscription.setIdElement(new IdType("Subscription", 123L));
ResourceModifiedMessage msg = new ResourceModifiedMessage(myContext, patient, ResourceModifiedMessage.OperationTypeEnum.CREATE);
msg.setSubscriptionId("Subscription/123");
msg.setId(new IdType("Patient/ABC"));
SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(criteria, msg);
SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(subscription, msg);
fail();
} catch (InternalErrorException e){
assertEquals("Failure processing resource ID[Patient/ABC] for subscription ID[Subscription/123]: Invalid resource reference found at path[Patient.managingOrganization] - Does not contain resource type - urn:uuid:13720262-b392-465f-913e-54fb198ff954", e.getMessage());
} catch (AssertionError e){
assertEquals("Reference at managingOrganization is invalid: urn:uuid:13720262-b392-465f-913e-54fb198ff954", e.getMessage());
}
}
@ -410,7 +425,7 @@ public class InMemorySubscriptionMatcherTestR4 {
}
@Test
public void testSearchStringParam() throws Exception {
public void testSearchStringParam() {
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("001");
patient.addName().setFamily("Tester_testSearchStringParam").addGiven("Joe");
@ -565,13 +580,11 @@ public class InMemorySubscriptionMatcherTestR4 {
@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();
@ -636,7 +649,6 @@ public class InMemorySubscriptionMatcherTestR4 {
o2.setValue(q2);
SearchParameterMap map;
IBundleProvider found;
QuantityParam param;
map = new SearchParameterMap();
@ -678,9 +690,7 @@ public class InMemorySubscriptionMatcherTestR4 {
Patient pt1 = new Patient();
pt1.addName().setFamily("ABCDEFGHIJK");
List<String> ids;
SearchParameterMap map;
IBundleProvider results;
// Contains = true
map = new SearchParameterMap();
@ -871,7 +881,7 @@ public class InMemorySubscriptionMatcherTestR4 {
map.add(Observation.SP_DATE, new DateParam("2011-01-02"));
for (Observation obs : nlist) {
// assertNotMatched(obs, map);
assertNotMatched(obs, map);
}
for (Observation obs : ylist) {
ourLog.info("Obs {} has time {}", obs.getId(), obs.getEffectiveDateTimeType().getValue().toString());

View File

@ -57,7 +57,7 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
@Before
public void beforeSetSubscriptionActivatingInterceptor() {
SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
mySubscriptionLoader.initSubscriptions();
mySubscriptionLoader.syncSubscriptions();
}
@ -109,7 +109,7 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
createSubscription(criteria2, payload, ourListenerServerBase);
mySubscriptionTestUtil.registerRestHookInterceptor();
mySubscriptionLoader.initSubscriptions();
mySubscriptionLoader.syncSubscriptions();
sendObservation(code, "SNOMED-CT");

View File

@ -113,10 +113,8 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
observation.setStatus(ObservationStatusEnum.FINAL);
MethodOutcome methodOutcome = ourClient.create().resource(observation).execute();
String observationId = methodOutcome.getId().getIdPart();
observation.setId(observationId);
IIdType id = myObservationDao.create(observation).getId();
observation.setId(id);
return observation;
}

View File

@ -4,7 +4,10 @@ package ca.uhn.fhir.jpa.subscription.resthook;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
import ca.uhn.fhir.jpa.subscription.NotificationServlet;
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;
@ -49,6 +52,8 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
@Autowired
private SubscriptionTestUtil mySubscriptionTestUtil;
private static NotificationServlet ourNotificationServlet;
private static String ourNotificationListenerServer;
@After
public void afterUnregisterRestHookListener() {
@ -79,9 +84,15 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
ourCreatedObservations.clear();
ourUpdatedObservations.clear();
ourContentTypes.clear();
ourNotificationServlet.reset();
}
private Subscription createSubscription(String theCriteria, String thePayload, String theEndpoint) throws InterruptedException {
private Subscription createSubscription(String criteria, String payload, String endpoint) throws InterruptedException {
return createSubscription(criteria, payload, endpoint, null);
}
private Subscription createSubscription(String theCriteria, String thePayload, String theEndpoint,
List<StringType> headers) throws InterruptedException {
Subscription subscription = new Subscription();
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED);
@ -91,15 +102,17 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
channel.setType(Subscription.SubscriptionChannelType.RESTHOOK);
channel.setPayload(thePayload);
channel.setEndpoint(theEndpoint);
if (headers != null) {
channel.setHeader(headers);
}
subscription.setChannel(channel);
MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute();
subscription.setId(methodOutcome.getId().getIdPart());
mySubscriptionIds.add(methodOutcome.getId());
waitForQueueToDrain();
return subscription;
return (Subscription)methodOutcome.getResource();
}
private Observation sendObservation(String code, String system) {
@ -120,6 +133,55 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
return observation;
}
@Test
public void testRestHookSubscription() throws Exception {
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
createSubscription(criteria1, null, ourNotificationListenerServer,
Collections.singletonList(new StringType("Authorization: abc-def")));
createSubscription(criteria2, null, ourNotificationListenerServer);
sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification with authorization header
waitForSize(1, ourNotificationServlet.getReceivedAuthorizationHeaders());
Assert.assertEquals(1, ourNotificationServlet.getReceivedNotificationCount());
Assert.assertEquals("abc-def", ourNotificationServlet.getReceivedAuthorizationHeaders().get(0));
ourNotificationServlet.reset();
sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification with authorization header
waitForSize(1, ourNotificationServlet.getReceivedAuthorizationHeaders());
Assert.assertEquals(1, ourNotificationServlet.getReceivedNotificationCount());
Assert.assertEquals("abc-def", ourNotificationServlet.getReceivedAuthorizationHeaders().get(0));
ourNotificationServlet.reset();
Observation observationTemp3 = sendObservation(code, "SNOMED-CT");
/// Should see 1 subscription notification with authorization header
waitForSize(1, ourNotificationServlet.getReceivedAuthorizationHeaders());
Assert.assertEquals(1, ourNotificationServlet.getReceivedNotificationCount());
Assert.assertEquals("abc-def", ourNotificationServlet.getReceivedAuthorizationHeaders().get(0));
ourNotificationServlet.reset();
Observation observation3 = ourClient.read(Observation.class, observationTemp3.getId());
CodeableConcept codeableConcept = new CodeableConcept();
observation3.setCode(codeableConcept);
Coding coding = codeableConcept.addCoding();
coding.setCode(code + "111");
coding.setSystem("SNOMED-CT");
ourClient.update().resource(observation3).withId(observation3.getIdElement()).execute();
// Should see 2 subscription notifications with and without authorization header
waitForSize(1, ourNotificationServlet.getReceivedAuthorizationHeaders());
Assert.assertEquals(1, ourNotificationServlet.getReceivedNotificationCount());
Assert.assertNull(ourNotificationServlet.getReceivedAuthorizationHeaders().get(0));
ourNotificationServlet.reset();
}
@Test
public void testRestHookSubscriptionApplicationFhirJson() throws Exception {
String payload = "application/fhir+json";
@ -291,7 +353,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
waitForSize(0, ourCreatedObservations);
waitForSize(5, ourUpdatedObservations);
Assert.assertFalse(subscription1.getId().equals(subscription2.getId()));
Assert.assertNotEquals(subscription1.getId(), subscription2.getId());
Assert.assertFalse(observation1.getId().isEmpty());
Assert.assertFalse(observation2.getId().isEmpty());
}
@ -353,16 +415,70 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
mySubscriptionTestUtil.waitForQueueToDrain();
}
@Test
public void testSubscriptionActivatesInMemoryTag() throws Exception {
String payload = "application/fhir+xml";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
Subscription subscriptionOrig = createSubscription(criteria1, payload, ourListenerServerBase);
IdType subscriptionId = subscriptionOrig.getIdElement();
assertEquals(Subscription.SubscriptionStatus.REQUESTED, subscriptionOrig.getStatus());
List<Coding> tags = subscriptionOrig.getMeta().getTag();
assertEquals(1, tags.size());
Coding tag = tags.get(0);
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY.toString(), tag.getCode());
assertEquals("In-memory", tag.getDisplay());
Subscription subscriptionActivated = ourClient.read().resource(Subscription.class).withId(subscriptionId.toUnqualifiedVersionless()).execute();
assertEquals(Subscription.SubscriptionStatus.ACTIVE, subscriptionActivated.getStatus());
tags = subscriptionActivated.getMeta().getTag();
assertEquals(1, tags.size());
tag = tags.get(0);
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY.toString(), tag.getCode());
assertEquals("In-memory", tag.getDisplay());
}
@Test
public void testSubscriptionActivatesDatabaseTag() throws Exception {
String payload = "application/fhir+xml";
Subscription subscriptionOrig = createSubscription("Observation?code=17861-6&context.type=IHD", payload, ourListenerServerBase);
IdType subscriptionId = subscriptionOrig.getIdElement();
List<Coding> tags = subscriptionOrig.getMeta().getTag();
assertEquals(1, tags.size());
Coding tag = tags.get(0);
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
assertEquals(SubscriptionMatchingStrategy.DATABASE.toString(), tag.getCode());
assertEquals("Database", tag.getDisplay());
Subscription subscription = ourClient.read().resource(Subscription.class).withId(subscriptionId.toUnqualifiedVersionless()).execute();
assertEquals(Subscription.SubscriptionStatus.ACTIVE, subscription.getStatus());
tags = subscription.getMeta().getTag();
assertEquals(1, tags.size());
tag = tags.get(0);
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
assertEquals(SubscriptionMatchingStrategy.DATABASE.toString(), tag.getCode());
assertEquals("Database", tag.getDisplay());
}
@BeforeClass
public static void startListenerServer() throws Exception {
ourListenerPort = PortUtil.findFreePort();
ourListenerRestServer = new RestfulServer(FhirContext.forDstu3());
ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context";
ourNotificationListenerServer = "http://localhost:" + ourListenerPort + "/fhir/subscription";
ObservationListener obsListener = new ObservationListener();
ourListenerRestServer.setResourceProviders(obsListener);
ourListenerServer = new Server(ourListenerPort);
ourNotificationServlet = new NotificationServlet();
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
@ -370,6 +486,9 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(ourListenerRestServer);
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
servletHolder = new ServletHolder();
servletHolder.setServlet(ourNotificationServlet);
proxyHandler.addServlet(servletHolder, "/fhir/subscription");
ourListenerServer.setHandler(proxyHandler);
ourListenerServer.start();

View File

@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.*;
/**
@ -112,6 +113,39 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
}
@Test
public void testPlaceholderReferencesInTransactionAreResolvedCorrectly() throws Exception {
String payload = "application/fhir+json";
String code = "1000000050";
String criteria1 = "Observation?";
createSubscription(criteria1, payload);
waitForActivatedSubscriptionCount(1);
// Create a transaction that should match
Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.TRANSACTION);
Patient patient = new Patient();
patient.setId(IdType.newRandomUuid());
patient.getIdentifierFirstRep().setSystem("foo").setValue("AAA");
bundle.addEntry().setResource(patient).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Patient");
Observation observation = new Observation();
observation.getIdentifierFirstRep().setSystem("foo").setValue("1");
observation.getCode().addCoding().setCode(code).setSystem("SNOMED-CT");
observation.setStatus(Observation.ObservationStatus.FINAL);
observation.getSubject().setReference(patient.getId());
bundle.addEntry().setResource(observation).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Observation");
// Send the transaction
mySystemDao.transaction(null, bundle);
waitForSize(1, ourUpdatedObservations);
assertThat(ourUpdatedObservations.get(0).getSubject().getReference(), matchesPattern("Patient/[0-9]+"));
}
@Test
public void testUpdatesHaveCorrectMetadataUsingTransactions() throws Exception {
String payload = "application/fhir+json";
@ -206,7 +240,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
waitForActivatedSubscriptionCount(1);
for (int i = 0; i < 5; i++) {
int changes = this.mySubscriptionLoader.doInitSubscriptionsForUnitTest();
int changes = this.mySubscriptionLoader.doSyncSubscriptionsForUnitTest();
assertEquals(0, changes);
}
}
@ -874,7 +908,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
sp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh();
mySearchParamRegistry.forceRefresh();
createSubscription(criteria, "application/json");
waitForActivatedSubscriptionCount(1);

View File

@ -69,7 +69,7 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
ourCreatedObservations.clear();
ourUpdatedObservations.clear();
mySubscriptionLoader.initSubscriptions();
mySubscriptionLoader.syncSubscriptions();
}
private void waitForQueueToDrain() throws InterruptedException {

View File

@ -0,0 +1,186 @@
package ca.uhn.fhir.jpa.subscription.resthook;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.StoppableSubscriptionDeliveringRestHookSubscriber;
import ca.uhn.fhir.jpa.model.interceptor.api.Hook;
import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorRegistry;
import ca.uhn.fhir.jpa.model.interceptor.api.Interceptor;
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionsR4Test;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.hasItem;
import static org.junit.Assert.*;
/**
* Test the rest-hook subscriptions
*/
@ContextConfiguration(classes = {RestHookWithInterceptorR4Test.MyTestCtxConfig.class})
public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(RestHookWithInterceptorR4Test.class);
private static boolean ourNextModifyResourceId;
private static boolean ourNextBeforeRestHookDeliveryReturn;
private static boolean ourHitBeforeRestHookDelivery;
private static boolean ourNextAfterRestHookDeliveryReturn;
private static boolean ourHitAfterRestHookDelivery;
private static boolean ourNextAddHeader;
private static FhirContext ourCtx = FhirContext.forR4();
@Autowired
StoppableSubscriptionDeliveringRestHookSubscriber myStoppableSubscriptionDeliveringRestHookSubscriber;
@After
public void cleanupStoppableSubscriptionDeliveringRestHookSubscriber() {
myStoppableSubscriptionDeliveringRestHookSubscriber.setCountDownLatch(null);
myStoppableSubscriptionDeliveringRestHookSubscriber.unPause();
}
@Override
@Before
public void before() throws Exception {
super.before();
ourNextModifyResourceId = false;
ourNextAddHeader = false;
ourNextBeforeRestHookDeliveryReturn = true;
ourNextAfterRestHookDeliveryReturn = true;
ourHitBeforeRestHookDelivery = false;
ourHitAfterRestHookDelivery = false;
}
@Test
public void testBeforeRestHookDelivery_ModifyResourceId() throws Exception {
ourNextModifyResourceId = true;
// Create a subscription
CountDownLatch registerLatch = registerLatchHookInterceptor(1, Pointcut.SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED);
createSubscription("Observation?status=final", "application/fhir+json");
registerLatch.await(10, TimeUnit.SECONDS);
// Creating a matching resource
CountDownLatch deliveryLatch = registerLatchHookInterceptor(1, Pointcut.SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY);
sendObservation();
deliveryLatch.await(10, TimeUnit.SECONDS);
assertEquals(0, ourCreatedObservations.size());
assertEquals(1, ourUpdatedObservations.size());
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
assertEquals("Observation/A", ourUpdatedObservations.get(0).getId());
assertTrue(ourHitBeforeRestHookDelivery);
assertTrue(ourHitAfterRestHookDelivery);
}
@Test
public void testBeforeRestHookDelivery_AddHeader() throws Exception {
ourNextAddHeader = true;
// Create a subscription
CountDownLatch registerLatch = registerLatchHookInterceptor(1, Pointcut.SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED);
createSubscription("Observation?status=final", "application/fhir+json");
registerLatch.await(10, TimeUnit.SECONDS);
// Creating a matching resource
CountDownLatch deliveryLatch = registerLatchHookInterceptor(1, Pointcut.SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY);
sendObservation();
deliveryLatch.await(10, TimeUnit.SECONDS);
assertEquals(0, ourCreatedObservations.size());
assertEquals(1, ourUpdatedObservations.size());
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
assertTrue(ourHitBeforeRestHookDelivery);
assertTrue(ourHitAfterRestHookDelivery);
assertThat(ourHeaders, hasItem("X-Foo: Bar"));
}
@Test
public void testBeforeRestHookDelivery_AbortDelivery() throws Exception {
ourNextBeforeRestHookDeliveryReturn = false;
// Create a subscription
CountDownLatch registerLatch = registerLatchHookInterceptor(1, Pointcut.SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED);
createSubscription("Observation?status=final", "application/fhir+json");
registerLatch.await(10, TimeUnit.SECONDS);
sendObservation();
Thread.sleep(1000);
assertEquals(0, ourUpdatedObservations.size());
}
protected Observation sendObservation() {
Observation observation = new Observation();
observation.setStatus(Observation.ObservationStatus.FINAL);
MethodOutcome methodOutcome = ourClient.create().resource(observation).execute();
observation.setId(methodOutcome.getId());
return observation;
}
@Configuration
static class MyTestCtxConfig {
@Autowired
private IInterceptorRegistry myInterceptorRegistry;
@Bean
public MyTestInterceptor interceptor() {
MyTestInterceptor retVal = new MyTestInterceptor();
myInterceptorRegistry.registerInterceptor(retVal);
return retVal;
}
}
/**
* Interceptor class
*/
@Interceptor
public static class MyTestInterceptor {
/**
* Constructor
*/
public MyTestInterceptor() {
ourLog.info("Creating interceptor");
}
@Hook(Pointcut.SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY)
public boolean beforeRestHookDelivery(ResourceDeliveryMessage theDeliveryMessage, CanonicalSubscription theSubscription) {
if (ourNextModifyResourceId) {
theDeliveryMessage.getPayload(ourCtx).setId(new IdType("Observation/A"));
}
if (ourNextAddHeader) {
theSubscription.addHeader("X-Foo: Bar");
}
ourHitBeforeRestHookDelivery = true;
return ourNextBeforeRestHookDeliveryReturn;
}
@Hook(Pointcut.SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY)
public boolean afterRestHookDelivery(ResourceDeliveryMessage theDeliveryMessage, CanonicalSubscription theSubscription) {
ourHitAfterRestHookDelivery = true;
return ourNextAfterRestHookDeliveryReturn;
}
}
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.entity.TermConceptMap;
import ca.uhn.fhir.jpa.entity.TermConceptMapGroup;
@ -8,12 +9,11 @@ import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
import org.hl7.fhir.r4.model.UriType;
import org.junit.AfterClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.*;
import org.junit.rules.ExpectedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -32,11 +32,26 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
public final ExpectedException expectedException = ExpectedException.none();
private IIdType myConceptMapId;
private void persistConceptMap() {
@Before
public void before() {
myDaoConfig.setAllowExternalReferences(true);
}
@After
public void after() {
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
}
private void createAndPersistConceptMap() {
ConceptMap conceptMap = createConceptMap();
persistConceptMap(conceptMap);
}
private void persistConceptMap(ConceptMap theConceptMap) {
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus theStatus) {
myConceptMapId = myConceptMapDao.create(createConceptMap(), mySrd).getId().toUnqualifiedVersionless();
myConceptMapId = myConceptMapDao.create(theConceptMap, mySrd).getId().toUnqualifiedVersionless();
}
});
}
@ -63,6 +78,17 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
}
@Test
public void testCreateConceptMapWithVirtualSourceSystem() {
ConceptMap conceptMap = createConceptMap();
conceptMap.getGroup().forEach(t->t.setSource(null));
conceptMap.setSource(new CanonicalType("http://hl7.org/fhir/uv/livd/StructureDefinition/loinc-livd"));
persistConceptMap(conceptMap);
}
@Test
public void testCreateConceptMapWithMissingTargetSystems() {
@ -113,17 +139,17 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testDuplicateConceptMapUrls() {
persistConceptMap();
createAndPersistConceptMap();
expectedException.expect(UnprocessableEntityException.class);
expectedException.expectMessage("Can not create multiple ConceptMap resources with ConceptMap.url \"http://example.com/my_concept_map\", already have one with resource ID: ConceptMap/" + myConceptMapId.getIdPart());
persistConceptMap();
createAndPersistConceptMap();
}
@Test
public void testStoreTermConceptMapAndChildren() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -301,7 +327,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateByCodeSystemsAndSourceCodeOneToMany() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -355,7 +381,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateByCodeSystemsAndSourceCodeOneToOne() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -429,7 +455,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateByCodeSystemsAndSourceCodeUnmapped() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -482,7 +508,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateUsingPredicatesWithCodeOnly() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -550,7 +576,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateUsingPredicatesWithSourceAndTargetSystem2() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -598,7 +624,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateUsingPredicatesWithSourceAndTargetSystem3() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -658,7 +684,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateUsingPredicatesWithSourceSystem() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -728,7 +754,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateUsingPredicatesWithSourceSystemAndVersion1() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -776,7 +802,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateUsingPredicatesWithSourceSystemAndVersion3() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -836,7 +862,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateUsingPredicatesWithSourceValueSet() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -906,7 +932,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateUsingPredicatesWithTargetValueSet() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -976,7 +1002,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateWithReverse() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -1025,7 +1051,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateWithReverseByCodeSystemsAndSourceCodeUnmapped() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -1048,7 +1074,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateWithReverseUsingPredicatesWithCodeOnly() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -1104,7 +1130,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateWithReverseUsingPredicatesWithSourceAndTargetSystem1() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -1153,7 +1179,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateWithReverseUsingPredicatesWithSourceAndTargetSystem4() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -1202,7 +1228,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateWithReverseUsingPredicatesWithSourceSystem() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -1260,7 +1286,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateWithReverseUsingPredicatesWithSourceSystemAndVersion() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -1320,7 +1346,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateWithReverseUsingPredicatesWithSourceValueSet() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
@ -1378,7 +1404,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Test
public void testTranslateWithReverseUsingPredicatesWithTargetValueSet() {
persistConceptMap();
createAndPersistConceptMap();
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));

View File

@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -78,7 +79,13 @@ public class AddIdGeneratorTask extends BaseTask<AddIdGeneratorTask> {
}
if (isNotBlank(sql)) {
if (JdbcUtils.getSequenceNames(getConnectionProperties()).contains(myGeneratorName)) {
Set<String> sequenceNames =
JdbcUtils.getSequenceNames(getConnectionProperties())
.stream()
.map(String::toLowerCase)
.collect(Collectors.toSet());
ourLog.debug("Currently have sequences: {}", sequenceNames);
if (sequenceNames.contains(myGeneratorName.toLowerCase())) {
ourLog.info("Sequence {} already exists - No action performed", myGeneratorName);
return;
}

View File

@ -35,6 +35,7 @@ public class AddTableRawSqlTask extends BaseTableTask<AddTableRawSqlTask> {
private static final Logger ourLog = LoggerFactory.getLogger(AddTableRawSqlTask.class);
private Map<DriverTypeEnum, List<String>> myDriverToSqls = new HashMap<>();
private List<String> myDriverNeutralSqls = new ArrayList<>();
public void addSql(DriverTypeEnum theDriverType, @Language("SQL") String theSql) {
Validate.notNull(theDriverType);
@ -52,9 +53,11 @@ public class AddTableRawSqlTask extends BaseTableTask<AddTableRawSqlTask> {
return;
}
List<String> sqlStatements = myDriverToSqls.get(getDriverType());
List<String> sqlStatements = myDriverToSqls.computeIfAbsent(getDriverType(), t -> new ArrayList<>());
sqlStatements.addAll(myDriverNeutralSqls);
ourLog.info("Going to create table {} using {} SQL statements", getTableName(), sqlStatements.size());
getConnectionProperties().getTxTemplate().execute(t->{
getConnectionProperties().getTxTemplate().execute(t -> {
JdbcTemplate jdbcTemplate = getConnectionProperties().newJdbcTemplate();
for (String nextSql : sqlStatements) {
@ -65,4 +68,9 @@ public class AddTableRawSqlTask extends BaseTableTask<AddTableRawSqlTask> {
});
}
public void addSql(String theSql) {
Validate.notBlank("theSql must not be null", theSql);
myDriverNeutralSqls.add(theSql);
}
}

View File

@ -329,6 +329,10 @@ public class BaseMigrationTasks<T extends Enum> {
myTask.addSql(theDriverTypeEnum, theSql);
return this;
}
public void addSql(@Language("SQL") String theSql) {
myTask.addSql(theSql);
}
}
public class BuilderAddTableByColumns implements IAcceptsTasks {

View File

@ -1,6 +1,8 @@
package ca.uhn.fhir.jpa.migrate.taskdef;
import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.util.VersionEnum;
import org.junit.Test;
import java.util.List;
@ -66,4 +68,35 @@ public class ArbitrarySqlTaskTest extends BaseTest {
getMigrator().migrate();
}
private static class TestUpdateTasks extends BaseMigrationTasks<VersionEnum> {
public TestUpdateTasks() {
Builder v = forVersion(VersionEnum.V3_5_0);
v
.addTableRawSql("A")
.addSql("delete from TEST_UPDATE_TASK where RES_TYPE = 'Patient'");
}
}
@Test
public void testUpdateTask() {
executeSql("create table TEST_UPDATE_TASK (PID bigint not null, RES_TYPE varchar(255), PARAM_NAME varchar(255))");
executeSql("insert into TEST_UPDATE_TASK (PID, RES_TYPE, PARAM_NAME) values (1, 'Patient', 'identifier')");
List<Map<String, Object>> rows = executeQuery("select * from TEST_UPDATE_TASK");
assertEquals(1, rows.size());
TestUpdateTasks migrator = new TestUpdateTasks();
getMigrator().addTasks(migrator.getTasks(VersionEnum.V3_3_0, VersionEnum.V3_6_0));
getMigrator().migrate();
rows = executeQuery("select * from TEST_UPDATE_TASK");
assertEquals(0, rows.size());
}
}

View File

@ -72,12 +72,53 @@
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-orm</artifactId>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<exclusions>
<exclusion>
<artifactId>xml-apis</artifactId>
<groupId>xml-apis</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.jscience</groupId>
<artifactId>jscience</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<!-- Java -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -40,7 +40,7 @@ public class ModelConfig {
* <li><code>"http://hl7.org/fhir/StructureDefinition/*"</code></li>
* </ul>
*/
public static final Set<String> DEFAULT_LOGICAL_BASE_URLS = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
public static final Set<String> DEFAULT_LOGICAL_BASE_URLS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"http://hl7.org/fhir/ValueSet/*",
"http://hl7.org/fhir/CodeSystem/*",
"http://hl7.org/fhir/valueset-*",
@ -57,6 +57,7 @@ public class ModelConfig {
private boolean myDefaultSearchParamsCanBeOverridden = false;
private Set<Subscription.SubscriptionChannelType> mySupportedSubscriptionTypes = new HashSet<>();
private String myEmailFromAddress = "noreply@unknown.com";
private boolean mySubscriptionMatchingEnabled = true;
/**
* If set to {@code true} the default search params (i.e. the search parameters that are
@ -225,7 +226,7 @@ public class ModelConfig {
}
}
HashSet<String> treatBaseUrlsAsLocal = new HashSet<String>();
HashSet<String> treatBaseUrlsAsLocal = new HashSet<>();
for (String next : ObjectUtils.defaultIfNull(theTreatBaseUrlsAsLocal, new HashSet<String>())) {
while (next.endsWith("/")) {
next = next.substring(0, next.length() - 1);
@ -320,6 +321,27 @@ public class ModelConfig {
return Collections.unmodifiableSet(mySupportedSubscriptionTypes);
}
/**
* If set to <code>true</code> (default is true) the server will match incoming resources against active subscriptions
* and send them to the subscription channel. If set to <code>false</code> no matching or sending occurs.
* @since 3.7.0
*/
public boolean isSubscriptionMatchingEnabled() {
return mySubscriptionMatchingEnabled;
}
/**
* If set to <code>true</code> (default is true) the server will match incoming resources against active subscriptions
* and send them to the subscription channel. If set to <code>false</code> no matching or sending occurs.
* @since 3.7.0
*/
public void setSubscriptionMatchingEnabled(boolean theSubscriptionMatchingEnabled) {
mySubscriptionMatchingEnabled = theSubscriptionMatchingEnabled;
}
@VisibleForTesting
public void clearSupportedSubscriptionTypesForUnitTest() {
mySupportedSubscriptionTypes.clear();

View File

@ -0,0 +1,48 @@
package ca.uhn.fhir.jpa.model.interceptor.api;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2019 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.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation should be placed on
* {@link Interceptor Subscription Interceptor}
* bean methods.
* <p>
* Methods with this annotation are invoked immediately before a REST HOOK
* subscription delivery
* </p>
*
* @see Interceptor
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Hook {
/**
* Provides the specific point where this method should be invoked
*/
Pointcut[] value();
}

View File

@ -0,0 +1,82 @@
package ca.uhn.fhir.jpa.model.interceptor.api;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2019 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 com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class HookParams {
private ListMultimap<Class<?>, Object> myParams = ArrayListMultimap.create();
/**
* Constructor
*/
public HookParams() {
}
/**
* Constructor
*/
public HookParams(Object... theParams) {
for (Object next : theParams) {
add(next);
}
}
@SuppressWarnings("unchecked")
private <T> void add(T theNext) {
Class<T> nextClass = (Class<T>) theNext.getClass();
add(nextClass, theNext);
}
public <T> HookParams add(Class<T> theType, T theParam) {
myParams.put(theType, theParam);
return this;
}
@SuppressWarnings("unchecked")
public <T> T get(Class<T> theParamType, int theIndex) {
List<T> objects = (List<T>) myParams.get(theParamType);
T retVal = null;
if (objects.size() > theIndex) {
retVal = objects.get(theIndex);
}
return retVal;
}
/**
* Returns an unmodifiable multimap of the params, where the
* key is the param type and the value is the actual instance
*/
public ListMultimap<Class<?>, Object> getParamsForType() {
return Multimaps.unmodifiableListMultimap(myParams);
}
public Collection<Object> values() {
return Collections.unmodifiableCollection(myParams.values());
}
}

View File

@ -0,0 +1,36 @@
package ca.uhn.fhir.jpa.model.interceptor.api;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2019 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 com.google.common.annotations.VisibleForTesting;
/**
* This is currently only here for unit tests!
*
* DO NOT USE IN NON-TEST CODE. Maybe this will change in the future?
*/
@FunctionalInterface
@VisibleForTesting
public interface IAnonymousLambdaHook {
void invoke(HookParams theArgs);
}

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.jpa.model.interceptor.api;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2019 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 interface IInterceptorBroadcaster {
/**
* Invoke the interceptor methods
*/
boolean callHooks(Pointcut thePointcut, HookParams theParams);
/**
* Invoke the interceptor methods
*/
boolean callHooks(Pointcut thePointcut, Object... theParams);
}

View File

@ -0,0 +1,65 @@
package ca.uhn.fhir.jpa.model.interceptor.api;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2019 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 com.google.common.annotations.VisibleForTesting;
public interface IInterceptorRegistry {
int DEFAULT_ORDER = 0;
/**
* Register an interceptor. This method has no effect if the given interceptor is already registered.
*
* @param theInterceptor The interceptor to register
* @return Returns <code>true</code> if at least one valid hook method was found on this interceptor
*/
boolean registerInterceptor(Object theInterceptor);
/**
* Unregister an interceptor. This method has no effect if the given interceptor is not already registered.
*
* @param theInterceptor The interceptor to unregister
*/
void unregisterInterceptor(Object theInterceptor);
/**
* @deprecated to be removed
*/
@Deprecated
boolean registerGlobalInterceptor(Object theInterceptor);
/**
* @deprecated to be removed
*/
@Deprecated
void unregisterGlobalInterceptor(Object theInterceptor);
@VisibleForTesting
void registerAnonymousHookForUnitTest(Pointcut thePointcut, IAnonymousLambdaHook theHook);
@VisibleForTesting
void registerAnonymousHookForUnitTest(Pointcut thePointcut, int theOrder, IAnonymousLambdaHook theHook);
@VisibleForTesting
void clearAnonymousHookForUnitTest();
}

View File

@ -0,0 +1,41 @@
package ca.uhn.fhir.jpa.model.interceptor.api;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2019 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.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation declares a bean as a subscription interceptor
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Interceptor {
/**
* @return Declares that an interceptor should be manually registered with the registry,
* and should not auto-register using Spring autowiring.
*/
boolean manualRegistration() default false;
}

View File

@ -0,0 +1,179 @@
package ca.uhn.fhir.jpa.model.interceptor.api;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2019 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.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Value for {@link Hook#value()}
*/
public enum Pointcut {
/**
* Invoked immediately after the delivery of a REST HOOK subscription.
* <p>
* When this hook is called, all processing is complete so this hook should not
* make any changes to the parameters.
* </p>
* Hooks may accept the following parameters:
* <ul>
* <li>ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription</li>
* <li>ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage</li>
* </ul>
*/
SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY("ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage"),
/**
* Invoked immediately before the delivery of a REST HOOK subscription.
* <p>
* Hooks may make changes to the delivery payload, or make changes to the
* canonical subscription such as adding headers, modifying the channel
* endpoint, etc.
* </p>
* Hooks may accept the following parameters:
* <ul>
* <li>ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription</li>
* <li>ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage</li>
* </ul>
*/
SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY("ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage"),
/**
* Invoked whenever a persisted resource (a resource that has just been stored in the
* database via a create/update/patch/etc.) has been checked for whether any subscriptions
* were triggered as a result of the operation
* Hooks may accept the following parameters:
* <ul>
* <li>ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage</li>
* </ul>
*/
SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED("ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage"),
/**
* Invoked immediately after an active subscription is "registered". In HAPI FHIR, when
* a subscription
* <p>
* Hooks may make changes to the canonicalized subscription and this will have an effect
* on processing across this server. Note however that timing issues may occur, since the
* subscription is already technically live by the time this hook is called.
* </p>
* Hooks may accept the following parameters:
* <ul>
* <li>ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription</li>
* </ul>
*/
SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED("ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription"),
/**
* Invoked before a resource will be created, immediately before the resource
* is persisted to the database.
* <p>
* Hooks will have access to the contents of the resource being created
* and may choose to make modifications to it. These changes will be
* reflected in permanent storage.
* </p>
* Hooks may accept the following parameters:
* <ul>
* <li>org.hl7.fhir.instance.model.api.IBaseResource</li>
* </ul>
*/
OP_PRESTORAGE_RESOURCE_CREATED("org.hl7.fhir.instance.model.api.IBaseResource"),
/**
* Invoked before a resource will be created, immediately before the transaction
* is committed (after all validation and other business rules have successfully
* completed, and any other database activity is complete.
* <p>
* Hooks will have access to the contents of the resource being created
* but should generally not make any
* changes as storage has already occurred. Changes will not be reflected
* in storage, but may be reflected in the HTTP response.
* </p>
* Hooks may accept the following parameters:
* <ul>
* <li>org.hl7.fhir.instance.model.api.IBaseResource</li>
* </ul>
*/
OP_PRECOMMIT_RESOURCE_CREATED("org.hl7.fhir.instance.model.api.IBaseResource"),
/**
* Invoked before a resource will be created
* <p>
* Hooks will have access to the contents of the resource being deleted
* but should not make any changes as storage has already occurred
* </p>
* Hooks may accept the following parameters:
* <ul>
* <li>org.hl7.fhir.instance.model.api.IBaseResource</li>
* </ul>
*/
OP_PRECOMMIT_RESOURCE_DELETED("org.hl7.fhir.instance.model.api.IBaseResource"),
/**
* Invoked before a resource will be updated, immediately before the transaction
* is committed (after all validation and other business rules have successfully
* completed, and any other database activity is complete.
* <p>
* Hooks will have access to the contents of the resource being updated
* (both the previous and new contents) but should generally not make any
* changes as storage has already occurred. Changes will not be reflected
* in storage, but may be reflected in the HTTP response.
* </p>
* Hooks may accept the following parameters:
* <ul>
* <li>org.hl7.fhir.instance.model.api.IBaseResource (previous contents)</li>
* <li>org.hl7.fhir.instance.model.api.IBaseResource (new contents)</li>
* </ul>
*/
OP_PRECOMMIT_RESOURCE_UPDATED("org.hl7.fhir.instance.model.api.IBaseResource", "org.hl7.fhir.instance.model.api.IBaseResource"),
/**
* Invoked before a resource will be updated, immediately before the resource
* is persisted to the database.
* <p>
* Hooks will have access to the contents of the resource being updated
* (both the previous and new contents) and may choose to make modifications
* to the new contents of the resource. These changes will be reflected in
* permanent storage.
* </p>
* Hooks may accept the following parameters:
* <ul>
* <li>org.hl7.fhir.instance.model.api.IBaseResource (previous contents)</li>
* <li>org.hl7.fhir.instance.model.api.IBaseResource (new contents)</li>
* </ul>
*/
OP_PRESTORAGE_RESOURCE_UPDATED("org.hl7.fhir.instance.model.api.IBaseResource", "org.hl7.fhir.instance.model.api.IBaseResource"),
;
private final List<String> myParameterTypes;
Pointcut(String... theParameterTypes) {
myParameterTypes = Collections.unmodifiableList(Arrays.asList(theParameterTypes));
}
public List<String> getParameterTypes() {
return myParameterTypes;
}
}

View File

@ -0,0 +1,372 @@
package ca.uhn.fhir.jpa.model.interceptor.executor;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.model.interceptor.api.*;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Nonnull;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@Component
public class InterceptorService implements IInterceptorRegistry, IInterceptorBroadcaster {
private static final Logger ourLog = LoggerFactory.getLogger(InterceptorService.class);
private final List<Object> myInterceptors = new ArrayList<>();
private final ListMultimap<Pointcut, BaseInvoker> myInvokers = ArrayListMultimap.create();
private final ListMultimap<Pointcut, BaseInvoker> myAnonymousInvokers = ArrayListMultimap.create();
private final Object myRegistryMutex = new Object();
/**
* Constructor
*/
public InterceptorService() {
super();
}
@VisibleForTesting
List<Object> getGlobalInterceptorsForUnitTest() {
return myInterceptors;
}
@Override
@VisibleForTesting
public void registerAnonymousHookForUnitTest(Pointcut thePointcut, IAnonymousLambdaHook theHook) {
registerAnonymousHookForUnitTest(thePointcut, DEFAULT_ORDER, theHook);
}
@Override
public void registerAnonymousHookForUnitTest(Pointcut thePointcut, int theOrder, IAnonymousLambdaHook theHook) {
Validate.notNull(thePointcut);
Validate.notNull(theHook);
myAnonymousInvokers.put(thePointcut, new AnonymousLambdaInvoker(theHook, theOrder));
}
@Override
@VisibleForTesting
public void clearAnonymousHookForUnitTest() {
myAnonymousInvokers.clear();
}
@Override
public boolean registerInterceptor(Object theInterceptor) {
synchronized (myRegistryMutex) {
if (isInterceptorAlreadyRegistered(theInterceptor)) {
return false;
}
Class<?> interceptorClass = theInterceptor.getClass();
int typeOrder = determineOrder(interceptorClass);
if (!scanInterceptorForHookMethodsAndAddThem(theInterceptor, typeOrder)) {
return false;
}
myInterceptors.add(theInterceptor);
// Make sure we're always sorted according to the order declared in
// @Order
sortByOrderAnnotation(myInterceptors);
for (Pointcut nextPointcut : myInvokers.keys()) {
List<BaseInvoker> nextInvokerList = myInvokers.get(nextPointcut);
nextInvokerList.sort(Comparator.naturalOrder());
}
return true;
}
}
private boolean scanInterceptorForHookMethodsAndAddThem(Object theInterceptor, int theTypeOrder) {
boolean retVal = false;
for (Method nextMethod : theInterceptor.getClass().getDeclaredMethods()) {
Hook hook = AnnotationUtils.findAnnotation(nextMethod, Hook.class);
if (hook != null) {
int methodOrder = theTypeOrder;
Order methodOrderAnnotation = AnnotationUtils.findAnnotation(nextMethod, Order.class);
if (methodOrderAnnotation != null) {
methodOrder = methodOrderAnnotation.value();
}
HookInvoker invoker = new HookInvoker(hook, theInterceptor, nextMethod, methodOrder);
for (Pointcut nextPointcut : hook.value()) {
myInvokers.put(nextPointcut, invoker);
}
retVal = true;
}
}
return retVal;
}
private int determineOrder(Class<?> theInterceptorClass) {
int typeOrder = DEFAULT_ORDER;
Order typeOrderAnnotation = AnnotationUtils.findAnnotation(theInterceptorClass, Order.class);
if (typeOrderAnnotation != null) {
typeOrder = typeOrderAnnotation.value();
}
return typeOrder;
}
private boolean isInterceptorAlreadyRegistered(Object theInterceptor) {
for (Object next : myInterceptors) {
if (next == theInterceptor) {
return true;
}
}
return false;
}
@Override
public void unregisterInterceptor(Object theInterceptor) {
synchronized (myRegistryMutex) {
myInterceptors.removeIf(t -> t == theInterceptor);
myInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
}
}
@Override
public boolean registerGlobalInterceptor(Object theInterceptor) {
return registerInterceptor(theInterceptor);
}
@Override
public void unregisterGlobalInterceptor(Object theInterceptor) {
unregisterInterceptor(theInterceptor);
}
private void sortByOrderAnnotation(List<Object> theObjects) {
IdentityHashMap<Object, Integer> interceptorToOrder = new IdentityHashMap<>();
for (Object next : theObjects) {
Order orderAnnotation = next.getClass().getAnnotation(Order.class);
int order = orderAnnotation != null ? orderAnnotation.value() : 0;
interceptorToOrder.put(next, order);
}
theObjects.sort((a, b) -> {
Integer orderA = interceptorToOrder.get(a);
Integer orderB = interceptorToOrder.get(b);
return orderA - orderB;
});
}
@Override
public boolean callHooks(Pointcut thePointcut, Object... theParams) {
return callHooks(thePointcut, new HookParams(theParams));
}
@Override
public boolean callHooks(Pointcut thePointcut, HookParams theParams) {
assert haveAppropriateParams(thePointcut, theParams);
List<BaseInvoker> invokers = getInvokersForPointcut(thePointcut);
/*
* Call each hook in order
*/
for (BaseInvoker nextInvoker : invokers) {
boolean shouldContinue = nextInvoker.invoke(theParams);
if (!shouldContinue) {
return false;
}
}
return true;
}
@VisibleForTesting
List<Object> getInterceptorsWithInvokersForPointcut(Pointcut thePointcut) {
return getInvokersForPointcut(thePointcut)
.stream()
.map(BaseInvoker::getInterceptor)
.collect(Collectors.toList());
}
/**
* Returns an ordered list of invokers for the given pointcut. Note that
* a new and stable list is returned to.. do whatever you want with it.
*/
private List<BaseInvoker> getInvokersForPointcut(Pointcut thePointcut) {
List<BaseInvoker> invokers;
boolean haveAnonymousInvokers;
synchronized (myRegistryMutex) {
List<BaseInvoker> globalInvokers = myInvokers.get(thePointcut);
List<BaseInvoker> anonymousInvokers = myAnonymousInvokers.get(thePointcut);
invokers = ListUtils.union(anonymousInvokers, globalInvokers);
haveAnonymousInvokers = anonymousInvokers.isEmpty() == false;
}
if (haveAnonymousInvokers) {
invokers.sort(Comparator.naturalOrder());
}
return invokers;
}
/**
* Only call this when assertions are enabled, it's expensive
*/
boolean haveAppropriateParams(Pointcut thePointcut, HookParams theParams) {
Validate.isTrue(theParams.getParamsForType().values().size() == thePointcut.getParameterTypes().size(), "Wrong number of params for pointcut %s - Wanted %s but found %s", thePointcut.name(), toErrorString(thePointcut.getParameterTypes()), theParams.getParamsForType().values().stream().map(t -> t.getClass().getSimpleName()).sorted().collect(Collectors.toList()));
List<String> wantedTypes = new ArrayList<>(thePointcut.getParameterTypes());
ListMultimap<Class<?>, Object> givenTypes = theParams.getParamsForType();
for (Class<?> nextTypeClass : givenTypes.keySet()) {
String nextTypeName = nextTypeClass.getName();
for (Object nextParamValue : givenTypes.get(nextTypeClass)) {
Validate.isTrue(nextTypeClass.isAssignableFrom(nextParamValue.getClass()), "Invalid params for pointcut %s - %s is not of type %s", thePointcut.name(), nextParamValue.getClass(), nextTypeClass);
Validate.isTrue(wantedTypes.remove(nextTypeName), "Invalid params for pointcut %s - Wanted %s but missing %s", thePointcut.name(), toErrorString(thePointcut.getParameterTypes()), nextTypeName);
}
}
return true;
}
private abstract class BaseInvoker implements Comparable<BaseInvoker> {
private final int myOrder;
private final Object myInterceptor;
BaseInvoker(Object theInterceptor, int theOrder) {
myInterceptor = theInterceptor;
myOrder = theOrder;
}
public Object getInterceptor() {
return myInterceptor;
}
abstract boolean invoke(HookParams theParams);
@Override
public int compareTo(BaseInvoker o) {
return myOrder - o.myOrder;
}
}
private class AnonymousLambdaInvoker extends BaseInvoker {
private final IAnonymousLambdaHook myHook;
public AnonymousLambdaInvoker(IAnonymousLambdaHook theHook, int theOrder) {
super(theHook, theOrder);
myHook = theHook;
}
@Override
boolean invoke(HookParams theParams) {
myHook.invoke(theParams);
return true;
}
}
private class HookInvoker extends BaseInvoker {
private final boolean myReturnsBoolean;
private final Method myMethod;
private final Class<?>[] myParameterTypes;
private final int[] myParameterIndexes;
/**
* Constructor
*/
private HookInvoker(Hook theHook, @Nonnull Object theInterceptor, @Nonnull Method theHookMethod, int theOrder) {
super(theInterceptor, theOrder);
myParameterTypes = theHookMethod.getParameterTypes();
myMethod = theHookMethod;
Class<?> returnType = theHookMethod.getReturnType();
if (returnType.equals(boolean.class)) {
myReturnsBoolean = true;
} else {
Validate.isTrue(void.class.equals(returnType), "Method does not return boolean or void: %s", theHookMethod);
myReturnsBoolean = false;
}
myParameterIndexes = new int[myParameterTypes.length];
Map<Class<?>, AtomicInteger> typeToCount = new HashMap<>();
for (int i = 0; i < myParameterTypes.length; i++) {
AtomicInteger counter = typeToCount.computeIfAbsent(myParameterTypes[i], t -> new AtomicInteger(0));
myParameterIndexes[i] = counter.getAndIncrement();
}
}
/**
* @return Returns true/false if the hook method returns a boolean, returns true otherwise
*/
@Override
boolean invoke(HookParams theParams) {
Object[] args = new Object[myParameterTypes.length];
for (int i = 0; i < myParameterTypes.length; i++) {
Class<?> nextParamType = myParameterTypes[i];
int nextParamIndex = myParameterIndexes[i];
Object nextParamValue = theParams.get(nextParamType, nextParamIndex);
args[i] = nextParamValue;
}
// Invoke the method
try {
Object returnValue = myMethod.invoke(getInterceptor(), args);
if (myReturnsBoolean) {
return (boolean) returnValue;
} else {
return true;
}
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
if (targetException instanceof RuntimeException) {
throw ((RuntimeException) targetException);
} else {
throw new InternalErrorException(targetException);
}
} catch (Exception e) {
throw new InternalErrorException(e);
}
}
}
private static String toErrorString(List<String> theParameterTypes) {
return theParameterTypes
.stream()
.sorted()
.collect(Collectors.joining(","));
}
}

View File

@ -0,0 +1,268 @@
package ca.uhn.fhir.jpa.model.interceptor.executor;
import ca.uhn.fhir.jpa.model.interceptor.api.*;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Patient;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {InterceptorServiceTest.InterceptorRegistryTestCtxConfig.class})
public class InterceptorServiceTest {
private static boolean ourNext_beforeRestHookDelivery_Return1;
private static List<String> ourInvocations = new ArrayList<>();
private static IBaseResource ourLastResourceOne;
private static IBaseResource ourLastResourceTwoA;
private static IBaseResource ourLastResourceTwoB;
@Autowired
private InterceptorService myInterceptorRegistry;
@Autowired
private MyTestInterceptorOne myInterceptorOne;
@Autowired
private MyTestInterceptorTwo myInterceptorTwo;
@Autowired
private MyTestInterceptorManual myInterceptorManual;
@Test
public void testGlobalInterceptorsAreFound() {
List<Object> globalInterceptors = myInterceptorRegistry.getGlobalInterceptorsForUnitTest();
assertEquals(2, globalInterceptors.size());
assertTrue(globalInterceptors.get(0).getClass().toString(), globalInterceptors.get(0) instanceof MyTestInterceptorOne);
assertTrue(globalInterceptors.get(1).getClass().toString(), globalInterceptors.get(1) instanceof MyTestInterceptorTwo);
}
@Test
public void testManuallyRegisterGlobalInterceptor() {
// Register the manual interceptor (has @Order right in the middle)
myInterceptorRegistry.registerInterceptor(myInterceptorManual);
List<Object> globalInterceptors = myInterceptorRegistry.getGlobalInterceptorsForUnitTest();
assertEquals(3, globalInterceptors.size());
assertTrue(globalInterceptors.get(0).getClass().toString(), globalInterceptors.get(0) instanceof MyTestInterceptorOne);
assertTrue(globalInterceptors.get(1).getClass().toString(), globalInterceptors.get(1) instanceof MyTestInterceptorManual);
assertTrue(globalInterceptors.get(2).getClass().toString(), globalInterceptors.get(2) instanceof MyTestInterceptorTwo);
// Try to register again (should have no effect
myInterceptorRegistry.registerInterceptor(myInterceptorManual);
globalInterceptors = myInterceptorRegistry.getGlobalInterceptorsForUnitTest();
assertEquals(3, globalInterceptors.size());
assertTrue(globalInterceptors.get(0).getClass().toString(), globalInterceptors.get(0) instanceof MyTestInterceptorOne);
assertTrue(globalInterceptors.get(1).getClass().toString(), globalInterceptors.get(1) instanceof MyTestInterceptorManual);
assertTrue(globalInterceptors.get(2).getClass().toString(), globalInterceptors.get(2) instanceof MyTestInterceptorTwo);
// Make sure we have the right invokers in the right order
List<Object> invokers = myInterceptorRegistry.getInterceptorsWithInvokersForPointcut(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED);
assertSame(myInterceptorOne, invokers.get(0));
assertSame(myInterceptorManual, invokers.get(1));
assertSame(myInterceptorTwo, invokers.get(2));
// Finally, unregister it
myInterceptorRegistry.unregisterInterceptor(myInterceptorManual);
globalInterceptors = myInterceptorRegistry.getGlobalInterceptorsForUnitTest();
assertEquals(2, globalInterceptors.size());
assertTrue(globalInterceptors.get(0).getClass().toString(), globalInterceptors.get(0) instanceof MyTestInterceptorOne);
assertTrue(globalInterceptors.get(1).getClass().toString(), globalInterceptors.get(1) instanceof MyTestInterceptorTwo);
}
@Test
public void testInvokeGlobalInterceptorMethods() {
Patient patient = new Patient();
HookParams params = new HookParams()
.add(IBaseResource.class, patient);
boolean outcome = myInterceptorRegistry.callHooks(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED, params);
assertTrue(outcome);
assertThat(ourInvocations, contains("MyTestInterceptorOne.beforeRestHookDelivery", "MyTestInterceptorTwo.beforeRestHookDelivery"));
assertSame(patient, ourLastResourceTwoA);
assertNull(ourLastResourceTwoB);
assertSame(patient, ourLastResourceOne);
}
@Test
public void testInvokeGlobalInterceptorMethods_MethodAbortsProcessing() {
ourNext_beforeRestHookDelivery_Return1 = false;
Patient patient = new Patient();
HookParams params = new HookParams()
.add(IBaseResource.class, patient);
boolean outcome = myInterceptorRegistry.callHooks(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED, params);
assertFalse(outcome);
assertThat(ourInvocations, contains("MyTestInterceptorOne.beforeRestHookDelivery"));
}
@Test
public void testCallHooksInvokedWithWrongParameters() {
Integer msg = 123;
CanonicalSubscription subs = new CanonicalSubscription();
HookParams params = new HookParams(msg, subs);
try {
myInterceptorRegistry.callHooks(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED, params);
fail();
} catch (IllegalArgumentException e) {
assertEquals("Wrong number of params for pointcut OP_PRECOMMIT_RESOURCE_CREATED - Wanted org.hl7.fhir.instance.model.api.IBaseResource but found [CanonicalSubscription, Integer]", e.getMessage());
}
}
@Test
public void testValidateParamTypes() {
HookParams params = new HookParams();
params.add(IBaseResource.class, new Patient());
params.add(IBaseResource.class, new Patient());
boolean validated = myInterceptorRegistry.haveAppropriateParams(Pointcut.OP_PRECOMMIT_RESOURCE_UPDATED, params);
assertTrue(validated);
}
@Test
public void testValidateParamTypesMissingParam() {
HookParams params = new HookParams();
params.add(IBaseResource.class, new Patient());
try {
myInterceptorRegistry.haveAppropriateParams(Pointcut.OP_PRECOMMIT_RESOURCE_UPDATED, params);
fail();
} catch (IllegalArgumentException e) {
assertEquals("Wrong number of params for pointcut OP_PRECOMMIT_RESOURCE_UPDATED - Wanted org.hl7.fhir.instance.model.api.IBaseResource,org.hl7.fhir.instance.model.api.IBaseResource but found [Patient]", e.getMessage());
}
}
@Test
public void testValidateParamTypesExtraParam() {
HookParams params = new HookParams();
params.add(IBaseResource.class, new Patient());
params.add(IBaseResource.class, new Patient());
params.add(IBaseResource.class, new Patient());
try {
myInterceptorRegistry.haveAppropriateParams(Pointcut.OP_PRECOMMIT_RESOURCE_UPDATED, params);
fail();
} catch (IllegalArgumentException e) {
assertEquals("Wrong number of params for pointcut OP_PRECOMMIT_RESOURCE_UPDATED - Wanted org.hl7.fhir.instance.model.api.IBaseResource,org.hl7.fhir.instance.model.api.IBaseResource but found [Patient, Patient, Patient]", e.getMessage());
}
}
@SuppressWarnings("unchecked")
@Test
public void testValidateParamTypesWrongParam() {
HookParams params = new HookParams();
Class clazz = IBaseResource.class;
params.add(clazz, "AAA");
params.add(clazz, "BBB");
try {
myInterceptorRegistry.haveAppropriateParams(Pointcut.OP_PRECOMMIT_RESOURCE_UPDATED, params);
fail();
} catch (IllegalArgumentException e) {
assertEquals("Invalid params for pointcut OP_PRECOMMIT_RESOURCE_UPDATED - class java.lang.String is not of type interface org.hl7.fhir.instance.model.api.IBaseResource", e.getMessage());
}
}
@Before
public void before() {
ourNext_beforeRestHookDelivery_Return1 = true;
ourLastResourceOne = null;
ourLastResourceTwoA = null;
ourLastResourceTwoB = null;
ourInvocations.clear();
}
@Configuration
@ComponentScan(basePackages = "ca.uhn.fhir.jpa.model")
static class InterceptorRegistryTestCtxConfig {
@Autowired
private IInterceptorRegistry myInterceptorRegistry;
/**
* Note: Orders are deliberately reversed to make sure we get the orders right
* using the @Order annotation
*/
@Bean
public MyTestInterceptorTwo interceptor1() {
MyTestInterceptorTwo retVal = new MyTestInterceptorTwo();
myInterceptorRegistry.registerInterceptor(retVal);
return retVal;
}
/**
* Note: Orders are deliberately reversed to make sure we get the orders right
* using the @Order annotation
*/
@Bean
public MyTestInterceptorOne interceptor2() {
MyTestInterceptorOne retVal = new MyTestInterceptorOne();
myInterceptorRegistry.registerInterceptor(retVal);
return retVal;
}
@Bean
public MyTestInterceptorManual interceptorManual() {
return new MyTestInterceptorManual();
}
}
@Interceptor
@Order(100)
public static class MyTestInterceptorOne {
public MyTestInterceptorOne() {
super();
}
@Hook(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED)
public boolean beforeRestHookDelivery(IBaseResource theResource) {
ourLastResourceOne = theResource;
ourInvocations.add("MyTestInterceptorOne.beforeRestHookDelivery");
return ourNext_beforeRestHookDelivery_Return1;
}
}
@Interceptor
@Order(300)
public static class MyTestInterceptorTwo {
@Hook(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED)
public void beforeRestHookDelivery(IBaseResource theResource0, IBaseResource theResource1) {
ourLastResourceTwoA = theResource0;
ourLastResourceTwoB = theResource1;
ourInvocations.add("MyTestInterceptorTwo.beforeRestHookDelivery");
}
}
@Interceptor(manualRegistration = true)
@Order(200)
public static class MyTestInterceptorManual {
@Hook(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED)
public void beforeRestHookDelivery() {
ourInvocations.add("MyTestInterceptorManual.beforeRestHookDelivery");
}
}
/**
* Just a make-believe version of this class for the unit test
*/
private static class CanonicalSubscription {
}
/**
* Just a make-believe version of this class for the unit test
*/
private static class ResourceDeliveryMessage {
}
}

View File

@ -0,0 +1,15 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -87,36 +87,6 @@
<artifactId>javax.annotation-api</artifactId>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<exclusions>
<exclusion>
<artifactId>xml-apis</artifactId>
<groupId>xml-apis</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.jscience</groupId>
<artifactId>jscience</artifactId>
</dependency>
<!-- Java -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
<!-- Testing -->
<dependency>

Some files were not shown because too many files have changed in this diff Show More